added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.coronastats-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-coronastats" description="CoronaStats Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.coronastats/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link CoronaStatsBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class CoronaStatsBindingConstants {
public static final String BINDING_ID = "coronastats";
// World
public static final ThingTypeUID THING_TYPE_WORLD = new ThingTypeUID(BINDING_ID, "world");
public static final String STATS = "stats";
public static final String WORLD_LABEL = "Corona Statistics (World)";
// Country
public static final ThingTypeUID THING_TYPE_COUNTRY = new ThingTypeUID(BINDING_ID, "country");
// @formatter:off
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS =
Collections.unmodifiableSet(Stream
.of(THING_TYPE_WORLD, THING_TYPE_COUNTRY)
.collect(Collectors.toSet())
);
// @formatter:on
// Properties country
public static final String PROPERTY_COUNTRY = "country";
// Channels world/country
public static final String CHANNEL_CASES = "cases";
public static final String CHANNEL_NEW_CASES = "today_cases";
public static final String CHANNEL_DEATHS = "deaths";
public static final String CHANNEL_NEW_DEATHS = "today_deaths";
public static final String CHANNEL_RECOVERED = "recovered";
public static final String CHANNEL_ACTIVE = "active";
public static final String CHANNEL_CRITICAL = "critical";
public static final String CHANNEL_TESTS = "tests";
public static final String CHANNEL_UPDATED = "updated";
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal;
import static org.openhab.binding.coronastats.internal.CoronaStatsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.coronastats.internal.handler.CoronaStatsCountryHandler;
import org.openhab.binding.coronastats.internal.handler.CoronaStatsWorldHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link CoronaStatsHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.coronastats", service = ThingHandlerFactory.class)
public class CoronaStatsHandlerFactory extends BaseThingHandlerFactory {
private final HttpClient httpClient;
@Activate
public CoronaStatsHandlerFactory(final @Reference HttpClientFactory httpClientFactory) {
this.httpClient = httpClientFactory.getCommonHttpClient();
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_WORLD.equals(thingTypeUID)) {
return new CoronaStatsWorldHandler((Bridge) thing, httpClient);
} else if (THING_TYPE_COUNTRY.equals(thingTypeUID)) {
return new CoronaStatsCountryHandler(thing);
}
return null;
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link CoronaStatsPollingException} class is the exception for all polling errors.
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class CoronaStatsPollingException extends Exception {
private static final long serialVersionUID = 1L;
public CoronaStatsPollingException(String message) {
super(message);
}
public CoronaStatsPollingException(String message, Throwable throwable) {
super(message, throwable);
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Configuration for the {@link CoronaStatsCountryHandler}
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class CoronaStatsCountryConfiguration {
private String countryCode = "";
public String getCountryCode() {
return countryCode.toUpperCase();
}
public boolean isValid() {
return !"".equals(countryCode);
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Configuration for the {@link CoronaStatsWorldHandler}
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class CoronaStatsWorldConfiguration {
public int refresh = 30;
public boolean isValid() {
return refresh >= 15;
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal.discovery;
import static org.openhab.binding.coronastats.internal.CoronaStatsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link CoronaStatsDiscoveryService} create a default world thing.
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.coronastats")
public class CoronaStatsDiscoveryService extends AbstractDiscoveryService {
private static final ThingUID WORLD_THING_UID = new ThingUID(THING_TYPE_WORLD, STATS);
private static final int DISCOVER_TIMEOUT_SECONDS = 2;
private final Logger logger = LoggerFactory.getLogger(CoronaStatsDiscoveryService.class);
public CoronaStatsDiscoveryService() throws IllegalArgumentException {
super(SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS, true);
}
@Override
protected void startScan() {
logger.debug("Manual CoronaStats discovery scan.");
addWorld();
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Background CoronaStats discovery scan.");
addWorld();
}
private void addWorld() {
// @formatter:off
DiscoveryResult world = DiscoveryResultBuilder
.create(WORLD_THING_UID)
.withLabel(WORLD_LABEL)
.build();
// @formatter:on
thingDiscovered(world);
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal.dto;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* The {@link CoronaStats} class is internal CoronaStats structure.
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class CoronaStats {
@SerializedName("data")
private @Nullable Set<CoronaStatsCountry> countries;
@SerializedName("worldStats")
private @Nullable CoronaStatsWorld world;
public @Nullable CoronaStatsCountry getCountry(String countryCodeKey) {
final Set<CoronaStatsCountry> localCountries = countries;
if (localCountries != null) {
for (CoronaStatsCountry country : localCountries) {
if (country.getCountryCode().equals(countryCodeKey)) {
return country;
}
}
}
return null;
}
public @Nullable CoronaStatsWorld getWorld() {
return world;
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal.dto;
import static org.openhab.binding.coronastats.internal.CoronaStatsBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import javax.measure.quantity.Dimensionless;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import tec.uom.se.AbstractUnit;
/**
* The {@link CoronaStatsCountry} class holds the internal data representation of each Country
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class CoronaStatsCases {
private int cases = -1;
private int todayCases = -1;
private int deaths = -1;
private int todayDeaths = -1;
private int recovered = -1;
private int active = -1;
private int critical = -1;
protected Map<String, State> getCaseChannelsStateMap() {
Map<String, State> map = new HashMap<>();
map.put(CHANNEL_CASES, parseToState(cases));
map.put(CHANNEL_NEW_CASES, parseToState(todayCases));
map.put(CHANNEL_DEATHS, parseToState(deaths));
map.put(CHANNEL_NEW_DEATHS, parseToState(todayDeaths));
map.put(CHANNEL_RECOVERED, parseToState(recovered));
map.put(CHANNEL_ACTIVE, parseToState(active));
map.put(CHANNEL_CRITICAL, parseToState(critical));
return map;
}
protected State parseToState(int count) {
if (count == -1) {
return UnDefType.NULL;
} else {
return new QuantityType<Dimensionless>(count, AbstractUnit.ONE);
}
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal.dto;
import static org.openhab.binding.coronastats.internal.CoronaStatsBindingConstants.*;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link CoronaStatsCountry} class holds the internal data representation of each Country
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class CoronaStatsCountry extends CoronaStatsCases {
private String country = "";
private String countryCode = "";
private int tests = -1;
private long updated = -1;
public String getCountryCode() {
return countryCode;
}
public Map<String, String> getProperties() {
Map<String, String> map = new HashMap<>();
map.put(PROPERTY_COUNTRY, country);
return Collections.unmodifiableMap(map);
}
public Map<String, State> getChannelsStateMap() {
Map<String, State> map = super.getCaseChannelsStateMap();
map.put(CHANNEL_TESTS, parseToState(tests));
if (updated == -1) {
map.put(CHANNEL_UPDATED, UnDefType.NULL);
} else {
Date date = new Date(updated);
ZonedDateTime zoned = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
map.put(CHANNEL_UPDATED, new DateTimeType(zoned));
}
return Collections.unmodifiableMap(map);
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal.dto;
import java.util.Collections;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.types.State;
/**
* The {@link CoronaStatsWorld} class holds the internal data representation for world stats
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class CoronaStatsWorld extends CoronaStatsCases {
public Map<String, State> getChannelsStateMap() {
return Collections.unmodifiableMap(super.getCaseChannelsStateMap());
}
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.coronastats.internal.config.CoronaStatsCountryConfiguration;
import org.openhab.binding.coronastats.internal.dto.CoronaStats;
import org.openhab.binding.coronastats.internal.dto.CoronaStatsCountry;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link CoronaStatsCountryHandler} is the handler for country thing
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class CoronaStatsCountryHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(CoronaStatsCountryHandler.class);
private CoronaStatsCountryConfiguration thingConfig = new CoronaStatsCountryConfiguration();
public CoronaStatsCountryHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
thingConfig = getConfigAs(CoronaStatsCountryConfiguration.class);
logger.debug("Initializing Corona Stats country handler for country code {}", thingConfig.getCountryCode());
if (thingConfig.isValid()) {
CoronaStatsWorldHandler handler = getBridgeHandler();
if (handler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge handler missing");
} else {
updateStatus(ThingStatus.ONLINE);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No valid country code given.");
}
}
@Override
public void dispose() {
logger.debug("CoronaStats country handler disposes.");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
refresh();
}
}
private void refresh() {
CoronaStatsWorldHandler handler = getBridgeHandler();
if (handler != null) {
CoronaStats coronaStats = handler.getCoronaStats();
if (coronaStats != null) {
notifyOnUpdate(coronaStats);
}
}
}
private @Nullable CoronaStatsWorldHandler getBridgeHandler() {
Bridge bridge = getBridge();
if (bridge != null) {
ThingHandler handler = bridge.getHandler();
if (handler instanceof CoronaStatsWorldHandler) {
return (CoronaStatsWorldHandler) handler;
}
}
return null;
}
public void notifyOnUpdate(CoronaStats coronaStats) {
CoronaStatsCountry country = coronaStats.getCountry(thingConfig.getCountryCode());
if (country == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Country not found");
return;
}
updateStatus(ThingStatus.ONLINE);
updateProperties(country.getProperties());
country.getChannelsStateMap().forEach(this::updateState);
}
}

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.coronastats.internal.handler;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.coronastats.internal.CoronaStatsPollingException;
import org.openhab.binding.coronastats.internal.config.CoronaStatsWorldConfiguration;
import org.openhab.binding.coronastats.internal.dto.CoronaStats;
import org.openhab.binding.coronastats.internal.dto.CoronaStatsWorld;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* The {@link CoronaStatsWorldHandler} is the handler for bridge thing
*
* @author Johannes Ott - Initial contribution
*/
@NonNullByDefault
public class CoronaStatsWorldHandler extends BaseBridgeHandler {
private static final String CORONASTATS_URL = "https://corona-stats.online/?format=json";
private final Logger logger = LoggerFactory.getLogger(CoronaStatsWorldHandler.class);
private CoronaStatsWorldConfiguration worldConfig = new CoronaStatsWorldConfiguration();
private @Nullable ScheduledFuture<?> pollingJob;
private @Nullable CoronaStats coronaStats;
private final Set<CoronaStatsCountryHandler> countryListeners = ConcurrentHashMap.newKeySet();
private final HttpClient client;
private final Gson gson = new Gson();
public CoronaStatsWorldHandler(Bridge bridge, HttpClient client) {
super(bridge);
this.client = client;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
final CoronaStats localCoronaStats = coronaStats;
if (localCoronaStats != null) {
notifyOnUpdate(localCoronaStats);
}
}
}
@Override
public void initialize() {
logger.debug("Initializing Corona Stats bridge handler");
worldConfig = getConfigAs(CoronaStatsWorldConfiguration.class);
if (worldConfig.isValid()) {
startPolling();
updateStatus(ThingStatus.UNKNOWN);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Refresh interval has to be at least 15 minutes.");
}
}
@Override
public void dispose() {
logger.debug("Handler disposed.");
stopPolling();
}
private void startPolling() {
final ScheduledFuture<?> localPollingJob = this.pollingJob;
if (localPollingJob == null || localPollingJob.isCancelled()) {
logger.debug("Start polling.");
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, worldConfig.refresh, TimeUnit.MINUTES);
}
}
private void stopPolling() {
final ScheduledFuture<?> localPollingJob = this.pollingJob;
if (localPollingJob != null && !localPollingJob.isCancelled()) {
logger.debug("Stop polling.");
localPollingJob.cancel(true);
pollingJob = null;
}
}
private void poll() {
logger.debug("Polling");
requestRefresh().handle((resultCoronaStats, pollException) -> {
if (resultCoronaStats == null) {
if (pollException == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
pollException.getMessage());
}
} else {
updateStatus(ThingStatus.ONLINE);
notifyOnUpdate(resultCoronaStats);
}
return null;
});
}
private CompletableFuture<@Nullable CoronaStats> requestRefresh() {
CompletableFuture<@Nullable CoronaStats> f = new CompletableFuture<>();
Request request = client.newRequest(URI.create(CORONASTATS_URL));
request.method(HttpMethod.GET).timeout(2000, TimeUnit.SECONDS).send(new BufferingResponseListener() {
@NonNullByDefault({})
@Override
public void onComplete(Result result) {
final HttpResponse response = (HttpResponse) result.getResponse();
if (result.getFailure() != null) {
Throwable e = result.getFailure();
if (e instanceof SocketTimeoutException || e instanceof TimeoutException) {
f.completeExceptionally(new CoronaStatsPollingException("Request timeout", e));
} else {
f.completeExceptionally(new CoronaStatsPollingException("Request failed", e));
}
} else if (response.getStatus() != 200) {
f.completeExceptionally(new CoronaStatsPollingException(getContentAsString()));
} else {
try {
CoronaStats coronaStatsJSON = gson.fromJson(getContentAsString(), CoronaStats.class);
f.complete(coronaStatsJSON);
} catch (JsonSyntaxException parseException) {
logger.error("Parsing failed: {}", parseException.getMessage());
f.completeExceptionally(new CoronaStatsPollingException("Parsing of response failed"));
}
}
}
});
return f;
}
@Override
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
if (childHandler instanceof CoronaStatsCountryHandler) {
logger.debug("Register thing listener.");
final CoronaStatsCountryHandler listener = (CoronaStatsCountryHandler) childHandler;
if (countryListeners.add(listener)) {
final CoronaStats localCoronaStats = coronaStats;
if (localCoronaStats != null) {
listener.notifyOnUpdate(localCoronaStats);
}
} else {
logger.warn("Tried to add listener {} but it was already present. This is probably an error.",
childHandler);
}
}
}
@Override
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
if (childHandler instanceof CoronaStatsCountryHandler) {
logger.debug("Unregister thing listener.");
if (!countryListeners.remove((CoronaStatsCountryHandler) childHandler)) {
logger.warn("Tried to remove listener {} but it was not registered. This is probably an error.",
childHandler);
}
}
}
public void notifyOnUpdate(@Nullable CoronaStats newCoronaStats) {
if (newCoronaStats != null) {
coronaStats = newCoronaStats;
CoronaStatsWorld world = newCoronaStats.getWorld();
if (world == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "World stats not found");
return;
}
world.getChannelsStateMap().forEach(this::updateState);
countryListeners.forEach(listener -> listener.notifyOnUpdate(newCoronaStats));
}
}
public @Nullable CoronaStats getCoronaStats() {
return coronaStats;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="coronastats" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>CoronaStats Binding</name>
<description>This is a binding for accessing data from https://corona-stats.online/ website.</description>
<author>Johannes Ott</author>
</binding:binding>

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="coronastats"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="cases">
<item-type>Number:Dimensionless</item-type>
<label>Total Cases</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="today_cases">
<item-type>Number:Dimensionless</item-type>
<label>New Cases</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="deaths">
<item-type>Number:Dimensionless</item-type>
<label>Total Deaths</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="today_deaths">
<item-type>Number:Dimensionless</item-type>
<label>New Deaths</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="recovered">
<item-type>Number:Dimensionless</item-type>
<label>Recovered</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="active">
<item-type>Number:Dimensionless</item-type>
<label>Active</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="critical">
<item-type>Number:Dimensionless</item-type>
<label>Critical</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="tests">
<item-type>Number:Dimensionless</item-type>
<label>Tests</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="updated">
<item-type>DateTime</item-type>
<label>Updated</label>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="coronastats"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="country">
<supported-bridge-type-refs>
<bridge-type-ref id="world"/>
</supported-bridge-type-refs>
<label>Corona Statistics (Country)</label>
<description>Corona statistics for a specific country</description>
<channels>
<channel id="cases" typeId="cases"/>
<channel id="today_cases" typeId="today_cases"/>
<channel id="deaths" typeId="deaths"/>
<channel id="today_deaths" typeId="today_deaths"/>
<channel id="recovered" typeId="recovered"/>
<channel id="active" typeId="active"/>
<channel id="critical" typeId="critical"/>
<channel id="tests" typeId="tests"/>
<channel id="updated" typeId="updated"/>
</channels>
<properties>
<property name="country"/>
</properties>
<config-description>
<parameter name="countryCode" type="text" required="true">
<label>Country Code</label>
<description>2-letter Country Code</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="coronastats"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="world">
<label>Corona Statistics (World)</label>
<description>Bridge for accessing data from https://corona-stats.online/ website and representing world statistics.</description>
<channels>
<channel id="cases" typeId="cases"/>
<channel id="today_cases" typeId="today_cases"/>
<channel id="deaths" typeId="deaths"/>
<channel id="today_deaths" typeId="today_deaths"/>
<channel id="recovered" typeId="recovered"/>
<channel id="active" typeId="active"/>
<channel id="critical" typeId="critical"/>
</channels>
<config-description>
<parameter name="refresh" type="integer" unit="min" min="15">
<default>30</default>
<label>Refresh Interval</label>
<description>Time between two API requests in minutes. Minimum 15 minutes.</description>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>