diff --git a/bundles/org.openhab.binding.netatmo/pom.xml b/bundles/org.openhab.binding.netatmo/pom.xml index b3b13ee0687cc..7e04a27d570b0 100644 --- a/bundles/org.openhab.binding.netatmo/pom.xml +++ b/bundles/org.openhab.binding.netatmo/pom.xml @@ -13,75 +13,9 @@ org.openhab.binding.netatmo openHAB Add-ons :: Bundles :: Netatmo Binding - -<<<<<<< Upstream, based on origin/main - - org.openhab.binding.netatmo.* - -<<<<<<< Upstream, based on origin/main - + - - org.openhab.osgiify - org.json.json - 20131018 - compile - - - com.squareup.okhttp - okhttp - 2.7.5 - compile - - - com.google.android - * - - - - - com.squareup.okhttp - logging-interceptor - 2.7.5 - compile - - - com.google.android - * - - - - - com.squareup.okio - okio - 1.6.0 - compile - - - io.gsonfire - gson-fire - 1.8.4 - compile - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.client - 1.0.0 - compile - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.common - 1.0.0 - compile - - - commons-codec - commons-codec - 1.8 - compile - - + com.google.code.gson gson 2.8.5 @@ -89,37 +23,4 @@ - - - - io.swagger.codegen.v3 - swagger-codegen-maven-plugin - 3.0.21 - - - - generate - - - https://raw.githubusercontent.com/cbornet/netatmo-swagger-decl/35e27745fb0d432bc6c8b5ec7a83ed2a09944cea/spec/swagger.yaml - java - false - false - - src/main/java - true - java8-localdatetime - true - - - - - - - - -======= ->>>>>>> 54453ef Rebase and spotless apply -======= ->>>>>>> 4201a80 Code cleansing diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/ChannelTypeUtils.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/ChannelTypeUtils.java deleted file mode 100644 index 909187a1895c2..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/ChannelTypeUtils.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright (c) 2010-2021 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.netatmo.internal; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; - -import javax.measure.Unit; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.io.net.http.HttpUtil; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.types.RawType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; - -/** - * This class holds various channel values conversion methods - * - * @author Gaël L'hopital - Initial contribution - * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules - * - */ -@NonNullByDefault -public class ChannelTypeUtils { - - public static State toStringType(@Nullable String value) { - return (value == null) ? UnDefType.NULL : new StringType(value); - } - - public static ZonedDateTime toZonedDateTime(Integer netatmoTS, ZoneId zoneId) { - Instant i = Instant.ofEpochSecond(netatmoTS); - return ZonedDateTime.ofInstant(i, zoneId); - } - - public static State toDateTimeType(@Nullable Float netatmoTS, ZoneId zoneId) { - return netatmoTS == null ? UnDefType.NULL : toDateTimeType(toZonedDateTime(netatmoTS.intValue(), zoneId)); - } - - public static State toDateTimeType(@Nullable Integer netatmoTS, ZoneId zoneId) { - return netatmoTS == null ? UnDefType.NULL : toDateTimeType(toZonedDateTime(netatmoTS, zoneId)); - } - - public static State toDateTimeType(@Nullable ZonedDateTime zonedDateTime) { - return (zonedDateTime == null) ? UnDefType.NULL : new DateTimeType(zonedDateTime); - } - - public static State toDecimalType(@Nullable Float value) { - return (value == null) ? UnDefType.NULL : toDecimalType(new BigDecimal(value)); - } - - public static State toDecimalType(@Nullable Integer value) { - return (value == null) ? UnDefType.NULL : toDecimalType(new BigDecimal(value)); - } - - public static State toDecimalType(@Nullable Double value) { - return (value == null) ? UnDefType.NULL : toDecimalType(new BigDecimal(value)); - } - - public static State toDecimalType(float value) { - return toDecimalType(new BigDecimal(value)); - } - - public static State toDecimalType(double value) { - return toDecimalType(new BigDecimal(value)); - } - - public static State toDecimalType(@Nullable BigDecimal decimal) { - return decimal == null ? UnDefType.NULL : new DecimalType(decimal.setScale(2, RoundingMode.HALF_UP)); - } - - public static State toDecimalType(@Nullable String textualDecimal) { - return textualDecimal == null ? UnDefType.NULL : new DecimalType(textualDecimal); - } - - public static State toOnOffType(@Nullable String yesno) { - return "on".equalsIgnoreCase(yesno) ? OnOffType.ON : OnOffType.OFF; - } - - public static State toOnOffType(@Nullable Integer value) { - return value != null ? (value == 1 ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF; - } - - public static State toOnOffType(@Nullable Boolean value) { - return value != null ? (value ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF; - } - - public static State toQuantityType(@Nullable Float value, Unit unit) { - return value == null ? UnDefType.NULL : toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(@Nullable Integer value, Unit unit) { - return value == null ? UnDefType.NULL : toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(@Nullable Double value, Unit unit) { - return value == null ? UnDefType.NULL : toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(float value, Unit unit) { - return toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(int value, Unit unit) { - return toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(double value, Unit unit) { - return toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(@Nullable BigDecimal value, Unit unit) { - return value == null ? UnDefType.NULL : new QuantityType<>(value, unit); - } - - public static State toRawType(String pictureUrl) { - RawType picture = HttpUtil.downloadImage(pictureUrl); - return picture == null ? UnDefType.UNDEF : picture; - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoConstants.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoConstants.java index 03b1a1ff07606..c3c674243cea4 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoConstants.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoConstants.java @@ -83,16 +83,6 @@ public enum MeasureClass { public static final String NETATMO_BASE_URL = "https://api.netatmo.com/"; public static final String NETATMO_APP_URL = "https://app.netatmo.net/"; - // Units of measurement of the data delivered by the API - // public static final Unit TEMPERATURE_UNIT = SIUnits.CELSIUS; - // public static final Unit HUMIDITY_UNIT = Units.PERCENT; - // public static final Unit PRESSURE_UNIT = HECTO(SIUnits.PASCAL); - // public static final Unit WIND_SPEED_UNIT = SIUnits.KILOMETRE_PER_HOUR; - // public static final Unit WIND_DIRECTION_UNIT = Units.DEGREE_ANGLE; - // public static final Unit RAIN_UNIT = MILLI(SIUnits.METRE); - // public static final Unit CO2_UNIT = Units.PARTS_PER_MILLION; - // public static final Unit NOISE_UNIT = Units.DECIBEL; - public enum MeasureType { SUM_RAIN, @SerializedName("Temperature") @@ -144,9 +134,6 @@ public String getDescriptor() { } // Default unit associated with each kind of measurement - // public static final Map> MEASUREUNITS = Map.of(MeasureType.SUM_RAIN, RAIN_UNIT, - // MeasureType.TEMP, TEMPERATURE_UNIT, MeasureType.HUM, HUMIDITY_UNIT, MeasureType.CO2, CO2_UNIT, - // MeasureType.NOISE, NOISE_UNIT, MeasureType.PRESSURE, PRESSURE_UNIT, MeasureType.WIND, WIND_SPEED_UNIT); public static final Map MEASUREUNITS = Map.of(MeasureType.SUM_RAIN, MeasureClass.RAIN_QTTY, MeasureType.TEMP, MeasureClass.EXTERIOR_TEMPERATURE, MeasureType.HUM, MeasureClass.HUMIDITY, MeasureType.CO2, MeasureClass.CO2, MeasureType.NOISE, MeasureClass.NOISE, @@ -199,7 +186,7 @@ public static enum Scope { // Radio signal quality thresholds private static final int[] EMPTY_INT_ARRAY = new int[0]; - public static final int[] WIFI_SIGNAL_LEVELS = new int[] { 86, 71, 56 }; // Resp : bad, average, good + public static final int[] WIFI_SIGNAL_LEVELS = new int[] { 99, 84, 69, 54 }; // Resp : bad, average, good, full public static final int[] RADIO_SIGNAL_LEVELS = new int[] { 90, 80, 70, 60 }; // Resp : low, medium, high, full public static final int[] NO_RADIO = EMPTY_INT_ARRAY; diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java index 880491520685e..92ca5109d3855 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java @@ -81,14 +81,14 @@ public double getMeasurements(String deviceId, @Nullable String moduleId, Measur MeasureLimit limit) throws NetatmoException { List result = getmeasure(deviceId, moduleId, scale, new String[] { (limit.toString() + "_" + type.toString()).toLowerCase() }, 0, 0, 0, false, false); - return result.size() > 0 ? result.get(0).getSingleValue() : Double.NaN; + return !result.isEmpty() ? result.get(0).getSingleValue() : Double.NaN; } public double getMeasurements(String deviceId, @Nullable String moduleId, MeasureScale scale, MeasureType type) throws NetatmoException { List result = getmeasure(deviceId, moduleId, scale, new String[] { type.toString().toLowerCase() }, 0, 0, 0, false, false); - return result.size() > 0 ? result.get(0).getSingleValue() : Double.NaN; + return !result.isEmpty() ? result.get(0).getSingleValue() : Double.NaN; } public List getmeasure(String deviceId, @Nullable String moduleId, MeasureScale scale, diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMeasureBodyElem.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMeasureBodyElem.java index b02c3ec5df225..a923b1961454b 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMeasureBodyElem.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMeasureBodyElem.java @@ -41,9 +41,9 @@ public List> getValue() { } public Double getSingleValue() { - if (value.size() > 0) { + if (!value.isEmpty()) { List first = value.get(0); - if (first.size() > 0) { + if (!first.isEmpty()) { return first.get(0); } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryService.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryService.java deleted file mode 100644 index 7454cb92ac540..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryService.java +++ /dev/null @@ -1,253 +0,0 @@ -/** - * Copyright (c) 2010-2021 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.netatmo.internal.discovery; - -import static org.openhab.binding.netatmo.internal.APIUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler; -import org.openhab.binding.netatmo.internal.handler.NetatmoDataListener; -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.i18n.LocaleProvider; -import org.openhab.core.i18n.TranslationProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkUtil; - -import io.swagger.client.model.NAHealthyHomeCoach; -import io.swagger.client.model.NAMain; -import io.swagger.client.model.NAPlug; -import io.swagger.client.model.NAStationModule; -import io.swagger.client.model.NAWelcomeCamera; -import io.swagger.client.model.NAWelcomeHome; - -/** - * The {@link NetatmoModuleDiscoveryService} searches for available Netatmo - * devices and modules connected to the API console - * - * @author Gaël L'hopital - Initial contribution - * @author Ing. Peter Weiss - Welcome camera implementation - * - */ -@NonNullByDefault -public class NetatmoModuleDiscoveryService extends AbstractDiscoveryService implements NetatmoDataListener { - private static final int SEARCH_TIME = 5; - private final NetatmoBridgeHandler netatmoBridgeHandler; - - public NetatmoModuleDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler, LocaleProvider localeProvider, - TranslationProvider translationProvider) { - super(SUPPORTED_DEVICE_THING_TYPES_UIDS, SEARCH_TIME); - this.netatmoBridgeHandler = netatmoBridgeHandler; - this.localeProvider = localeProvider; - this.i18nProvider = translationProvider; - } - - @Override - public void activate(@Nullable Map configProperties) { - super.activate(configProperties); - netatmoBridgeHandler.registerDataListener(this); - } - - @Override - public void deactivate() { - netatmoBridgeHandler.unregisterDataListener(this); - super.deactivate(); - } - - @Override - public void startScan() { - if (netatmoBridgeHandler.configuration.readStation) { - netatmoBridgeHandler.getStationsDataBody(null).ifPresent(dataBody -> { - nonNullList(dataBody.getDevices()).forEach(station -> { - discoverWeatherStation(station); - }); - }); - } - if (netatmoBridgeHandler.configuration.readHealthyHomeCoach) { - netatmoBridgeHandler.getHomecoachDataBody(null).ifPresent(dataBody -> { - nonNullList(dataBody.getDevices()).forEach(homecoach -> { - discoverHomeCoach(homecoach); - }); - }); - } - if (netatmoBridgeHandler.configuration.readThermostat) { - netatmoBridgeHandler.getThermostatsDataBody(null).ifPresent(dataBody -> { - nonNullList(dataBody.getDevices()).forEach(plug -> { - discoverThermostat(plug); - }); - }); - } - if (netatmoBridgeHandler.configuration.readWelcome || netatmoBridgeHandler.configuration.readPresence) { - netatmoBridgeHandler.getWelcomeDataBody(null).ifPresent(dataBody -> { - nonNullList(dataBody.getHomes()).forEach(home -> { - discoverWelcomeHome(home); - }); - }); - } - } - - @Override - protected synchronized void stopScan() { - super.stopScan(); - removeOlderResults(getTimestampOfLastScan(), netatmoBridgeHandler.getThing().getUID()); - } - - @Override - public void onDataRefreshed(Object data) { - if (!isBackgroundDiscoveryEnabled()) { - return; - } - if (data instanceof NAMain) { - discoverWeatherStation((NAMain) data); - } else if (data instanceof NAPlug) { - discoverThermostat((NAPlug) data); - } else if (data instanceof NAHealthyHomeCoach) { - discoverHomeCoach((NAHealthyHomeCoach) data); - } else if (data instanceof NAWelcomeHome) { - discoverWelcomeHome((NAWelcomeHome) data); - } - } - - private void discoverThermostat(NAPlug plug) { - onDeviceAddedInternal(plug.getId(), null, plug.getType(), plug.getStationName(), plug.getFirmware()); - nonNullList(plug.getModules()).forEach(thermostat -> { - onDeviceAddedInternal(thermostat.getId(), plug.getId(), thermostat.getType(), thermostat.getModuleName(), - thermostat.getFirmware()); - }); - } - - private void discoverHomeCoach(NAHealthyHomeCoach homecoach) { - onDeviceAddedInternal(homecoach.getId(), null, homecoach.getType(), homecoach.getName(), - homecoach.getFirmware()); - } - - private void discoverWeatherStation(NAMain station) { - final boolean isFavorite = station.isFavorite() != null && station.isFavorite(); - final String weatherStationName = createWeatherStationName(station, isFavorite); - - onDeviceAddedInternal(station.getId(), null, station.getType(), weatherStationName, station.getFirmware()); - nonNullList(station.getModules()).forEach(module -> { - onDeviceAddedInternal(module.getId(), station.getId(), module.getType(), - createWeatherModuleName(station, module, isFavorite), module.getFirmware()); - }); - } - - private void discoverWelcomeHome(NAWelcomeHome home) { - // I observed that Thermostat homes are also reported here by Netatmo API - // So I ignore homes that have an empty list of cameras - List cameras = nonNullList(home.getCameras()); - if (!cameras.isEmpty()) { - onDeviceAddedInternal(home.getId(), null, WELCOME_HOME_THING_TYPE.getId(), home.getName(), null); - // Discover Cameras - cameras.forEach(camera -> { - onDeviceAddedInternal(camera.getId(), home.getId(), camera.getType(), camera.getName(), null); - }); - - // Discover Known Persons - nonNullStream(home.getPersons()).filter(person -> person.getPseudo() != null).forEach(person -> { - onDeviceAddedInternal(person.getId(), home.getId(), WELCOME_PERSON_THING_TYPE.getId(), - person.getPseudo(), null); - }); - } - } - - private void onDeviceAddedInternal(String id, @Nullable String parentId, String type, String name, - @Nullable Integer firmwareVersion) { - ThingUID thingUID = findThingUID(type, id); - Map properties = new HashMap<>(); - - properties.put(EQUIPMENT_ID, id); - if (parentId != null) { - properties.put(PARENT_ID, parentId); - } - if (firmwareVersion != null) { - properties.put(Thing.PROPERTY_VENDOR, VENDOR); - properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion); - properties.put(Thing.PROPERTY_MODEL_ID, type); - properties.put(Thing.PROPERTY_SERIAL_NUMBER, id); - } - addDiscoveredThing(thingUID, properties, name); - } - - private void addDiscoveredThing(ThingUID thingUID, Map properties, String displayLabel) { - DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties) - .withBridge(netatmoBridgeHandler.getThing().getUID()).withLabel(displayLabel) - .withRepresentationProperty(EQUIPMENT_ID).build(); - - thingDiscovered(discoveryResult); - } - - private ThingUID findThingUID(String thingType, String thingId) throws IllegalArgumentException { - for (ThingTypeUID supportedThingTypeUID : getSupportedThingTypes()) { - String uid = supportedThingTypeUID.getId(); - - if (uid.equalsIgnoreCase(thingType)) { - return new ThingUID(supportedThingTypeUID, netatmoBridgeHandler.getThing().getUID(), - thingId.replaceAll("[^a-zA-Z0-9_]", "")); - } - } - - throw new IllegalArgumentException("Unsupported device type discovered : " + thingType); - } - - private String createWeatherStationName(NAMain station, boolean isFavorite) { - StringBuilder nameBuilder = new StringBuilder(); - nameBuilder.append(localizeType(station.getType())); - if (station.getStationName() != null) { - nameBuilder.append(' '); - nameBuilder.append(station.getStationName()); - } - if (isFavorite) { - nameBuilder.append(" (favorite)"); - } - return nameBuilder.toString(); - } - - private String createWeatherModuleName(NAMain station, NAStationModule module, boolean isFavorite) { - StringBuilder nameBuilder = new StringBuilder(); - if (module.getModuleName() != null) { - nameBuilder.append(module.getModuleName()); - } else { - nameBuilder.append(localizeType(module.getType())); - } - if (station.getStationName() != null) { - nameBuilder.append(' '); - nameBuilder.append(station.getStationName()); - } - if (isFavorite) { - nameBuilder.append(" (favorite)"); - } - return nameBuilder.toString(); - } - - private String localizeType(String typeName) { - Bundle bundle = FrameworkUtil.getBundle(this.getClass()); - @Nullable - String localizedType = i18nProvider.getText(bundle, "thing-type.netatmo." + typeName + ".label", typeName, - localeProvider.getLocale()); - if (localizedType != null) { - return localizedType; - } - return typeName; - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoBridgeHandler.java deleted file mode 100644 index 61ce9fa850f94..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoBridgeHandler.java +++ /dev/null @@ -1,408 +0,0 @@ -/** - * Copyright (c) 2010-2021 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.netatmo.internal.handler; - -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; - -import org.apache.oltu.oauth2.client.OAuthClient; -import org.apache.oltu.oauth2.client.URLConnectionClient; -import org.apache.oltu.oauth2.client.request.OAuthClientRequest; -import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; -import org.apache.oltu.oauth2.common.message.types.GrantType; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.config.NetatmoBridgeConfiguration; -import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent; -import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEventPerson; -import org.openhab.binding.netatmo.internal.webhook.WelcomeWebHookServlet; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.Channel; -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.types.Command; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.swagger.client.ApiClient; -import io.swagger.client.ApiException; -import io.swagger.client.api.HealthyhomecoachApi; -import io.swagger.client.api.PartnerApi; -import io.swagger.client.api.StationApi; -import io.swagger.client.api.ThermostatApi; -import io.swagger.client.api.WelcomeApi; -import io.swagger.client.auth.Authentication; -import io.swagger.client.auth.OAuth; -import io.swagger.client.model.NAHealthyHomeCoachDataBody; -import io.swagger.client.model.NAMeasureBodyElem; -import io.swagger.client.model.NAStationDataBody; -import io.swagger.client.model.NAThermostatDataBody; -import io.swagger.client.model.NAWelcomeHomeData; - -/** - * {@link NetatmoBridgeHandler} is the handler for a Netatmo API and connects it - * to the framework. The devices and modules uses the - * {@link NetatmoBridgeHandler} to request informations about their status - * - * @author Gaël L'hopital - Initial contribution OH2 version - * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules - * - */ -@NonNullByDefault -public class NetatmoBridgeHandler extends BaseBridgeHandler { - private final Logger logger = LoggerFactory.getLogger(NetatmoBridgeHandler.class); - - public NetatmoBridgeConfiguration configuration = new NetatmoBridgeConfiguration(); - private @Nullable ScheduledFuture refreshJob; - private @Nullable APICreator apiCreator; - private @Nullable WelcomeWebHookServlet webHookServlet; - private List dataListeners = new CopyOnWriteArrayList<>(); - - private static class APICreator { - - private final ApiClient apiClient; - private final Map, Object> apiMap; - - private APICreator(ApiClient apiClient) { - super(); - this.apiClient = apiClient; - apiMap = new HashMap<>(); - } - - @SuppressWarnings("unchecked") - public T getAPI(Class apiClass) { - T api = (T) apiMap.get(apiClass); - if (api == null) { - try { - api = apiClass.getDeclaredConstructor(ApiClient.class).newInstance(apiClient); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException - | NoSuchMethodException e) { - throw new RuntimeException("Error on executing API class constructor!", e); - } - apiMap.put(apiClass, api); - } - return api; - } - } - - public NetatmoBridgeHandler(Bridge bridge, @Nullable WelcomeWebHookServlet webHookServlet) { - super(bridge); - this.webHookServlet = webHookServlet; - } - - @Override - public void initialize() { - logger.debug("Initializing Netatmo API bridge handler."); - - configuration = getConfigAs(NetatmoBridgeConfiguration.class); - scheduleTokenInitAndRefresh(); - } - - private void connectionSucceed() { - updateStatus(ThingStatus.ONLINE); - WelcomeWebHookServlet servlet = webHookServlet; - String webHookURI = getWebHookURI(); - if (servlet != null && webHookURI != null) { - getWelcomeApi().ifPresent(api -> { - servlet.activate(this); - logger.debug("Setting up Netatmo Welcome WebHook"); - api.addwebhook(webHookURI, WEBHOOK_APP); - }); - } - } - - private void scheduleTokenInitAndRefresh() { - refreshJob = scheduler.scheduleWithFixedDelay(() -> { - logger.debug("Initializing API Connection and scheduling token refresh every {}s", - configuration.reconnectInterval); - try { - initializeApiClient(); - // I use a connection to Netatmo API using PartnerAPI to ensure that API is reachable - getPartnerApi().partnerdevices(); - connectionSucceed(); - } catch (ApiException e) { - switch (e.getCode()) { - case 404: // If no partner station has been associated - likely to happen - we'll have this - // error - // but it means connection to API is OK - connectionSucceed(); - break; - case 403: // Forbidden Access maybe too many requests ? Let's wait next cycle - logger.warn("Error 403 while connecting to Netatmo API, will retry in {} s", - configuration.reconnectInterval); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Netatmo Access Forbidden, will retry in " + configuration.reconnectInterval - + " seconds."); - break; - default: - if (logger.isDebugEnabled()) { - // we also attach the stack trace - logger.error("Unable to connect Netatmo API : {}", e.getMessage(), e); - } else { - logger.error("Unable to connect Netatmo API : {}", e.getMessage()); - } - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "Unable to connect Netatmo API : " + e.getLocalizedMessage()); - } - } catch (RuntimeException e) { - if (logger.isDebugEnabled()) { - logger.warn("Unable to connect Netatmo API : {}", e.getMessage(), e); - } else { - logger.warn("Unable to connect Netatmo API : {}", e.getMessage()); - } - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Netatmo Access Failed, will retry in " + configuration.reconnectInterval + " seconds."); - } - // We'll do this every x seconds to guaranty token refresh - }, 2, configuration.reconnectInterval, TimeUnit.SECONDS); - } - - private void initializeApiClient() { - try { - ApiClient apiClient = new ApiClient(); - - OAuthClientRequest oAuthRequest = OAuthClientRequest.tokenLocation("https://api.netatmo.net/oauth2/token") - .setClientId(configuration.clientId).setClientSecret(configuration.clientSecret) - .setUsername(configuration.username).setPassword(configuration.password).setScope(getApiScope()) - .setGrantType(GrantType.PASSWORD).buildBodyMessage(); - - OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient()); - - OAuthJSONAccessTokenResponse accessTokenResponse = oAuthClient.accessToken(oAuthRequest, - OAuthJSONAccessTokenResponse.class); - String accessToken = accessTokenResponse.getAccessToken(); - - for (Authentication authentication : apiClient.getAuthentications().values()) { - if (authentication instanceof OAuth) { - ((OAuth) authentication).setAccessToken(accessToken); - } - } - - apiCreator = new APICreator(apiClient); - } catch (OAuthSystemException | OAuthProblemException e) { - throw new RuntimeException("Error on trying to get an access token!", e); - } - } - - private String getApiScope() { - List scopes = new ArrayList<>(); - - if (configuration.readStation) { - scopes.add("read_station"); - } - - if (configuration.readThermostat) { - scopes.add("read_thermostat"); - scopes.add("write_thermostat"); - } - - if (configuration.readHealthyHomeCoach) { - scopes.add("read_homecoach"); - } - - if (configuration.readWelcome) { - scopes.add("read_camera"); - scopes.add("access_camera"); - scopes.add("write_camera"); - } - - if (configuration.readPresence) { - scopes.add("read_presence"); - scopes.add("access_presence"); - } - - return String.join(" ", scopes); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - logger.debug("Netatmo Bridge is read-only and does not handle commands"); - } - - public @Nullable PartnerApi getPartnerApi() { - return apiCreator != null ? apiCreator.getAPI(PartnerApi.class) : null; - } - - public Optional getStationApi() { - return apiCreator != null ? Optional.of(apiCreator.getAPI(StationApi.class)) : Optional.empty(); - } - - public Optional getHomeCoachApi() { - return apiCreator != null ? Optional.of(apiCreator.getAPI(HealthyhomecoachApi.class)) : Optional.empty(); - } - - public Optional getThermostatApi() { - return apiCreator != null ? Optional.of(apiCreator.getAPI(ThermostatApi.class)) : Optional.empty(); - } - - public Optional getWelcomeApi() { - return apiCreator != null ? Optional.of(apiCreator.getAPI(WelcomeApi.class)) : Optional.empty(); - } - - @Override - public void dispose() { - logger.debug("Running dispose()"); - - WelcomeWebHookServlet servlet = webHookServlet; - if (servlet != null && getWebHookURI() != null) { - getWelcomeApi().ifPresent(api -> { - logger.debug("Releasing Netatmo Welcome WebHook"); - servlet.deactivate(); - api.dropwebhook(WEBHOOK_APP); - }); - } - - ScheduledFuture job = refreshJob; - if (job != null) { - job.cancel(true); - refreshJob = null; - } - } - - public Optional getStationsDataBody(@Nullable String equipmentId) { - Optional data = getStationApi().map(api -> api.getstationsdata(equipmentId, true).getBody()); - updateStatus(ThingStatus.ONLINE); - return data; - } - - public List getStationMeasureResponses(String equipmentId, @Nullable String moduleId, String scale, - List types) { - List data = getStationApi() - .map(api -> api.getmeasure(equipmentId, scale, types, moduleId, null, "last", 1, true, false).getBody()) - .orElse(null); - updateStatus(ThingStatus.ONLINE); - NAMeasureBodyElem element = data != null && !data.isEmpty() ? data.get(0) : null; - return element != null ? element.getValue().get(0) : Collections.emptyList(); - } - - public Optional getHomecoachDataBody(@Nullable String equipmentId) { - Optional data = getHomeCoachApi() - .map(api -> api.gethomecoachsdata(equipmentId).getBody()); - updateStatus(ThingStatus.ONLINE); - return data; - } - - public Optional getThermostatsDataBody(@Nullable String equipmentId) { - Optional data = getThermostatApi() - .map(api -> api.getthermostatsdata(equipmentId).getBody()); - updateStatus(ThingStatus.ONLINE); - return data; - } - - public Optional getWelcomeDataBody(@Nullable String homeId) { - Optional data = getWelcomeApi().map(api -> api.gethomedata(homeId, null).getBody()); - updateStatus(ThingStatus.ONLINE); - return data; - } - - /** - * Returns the Url of the picture - * - * @return Url of the picture or UnDefType.UNDEF - */ - public String getPictureUrl(@Nullable String id, @Nullable String key) { - StringBuilder ret = new StringBuilder(); - if (id != null && key != null) { - ret.append(WELCOME_PICTURE_URL).append("?").append(WELCOME_PICTURE_IMAGEID).append("=").append(id) - .append("&").append(WELCOME_PICTURE_KEY).append("=").append(key); - } - return ret.toString(); - } - - public Optional findNAThing(@Nullable String searchedId) { - List things = getThing().getThings(); - Stream naHandlers = things.stream().map(Thing::getHandler) - .filter(AbstractNetatmoThingHandler.class::isInstance).map(AbstractNetatmoThingHandler.class::cast) - .filter(handler -> handler.matchesId(searchedId)); - return naHandlers.findAny(); - } - - public void webHookEvent(NAWebhookCameraEvent event) { - // This currently the only known event type but I suspect usage can grow in the future... - if (event.getAppType() == NAWebhookCameraEvent.AppTypeEnum.CAMERA) { - Set modules = new HashSet<>(); - if (WELCOME_EVENTS.contains(event.getEventType()) || PRESENCE_EVENTS.contains(event.getEventType())) { - String cameraId = event.getCameraId(); - if (cameraId != null) { - Optional camera = findNAThing(cameraId); - camera.ifPresent(modules::add); - } - } - if (HOME_EVENTS.contains(event.getEventType())) { - String homeId = event.getHomeId(); - if (homeId != null) { - Optional home = findNAThing(homeId); - home.ifPresent(modules::add); - } - } - if (PERSON_EVENTS.contains(event.getEventType())) { - List persons = event.getPersons(); - persons.forEach(person -> { - String personId = person.getId(); - if (personId != null) { - Optional personHandler = findNAThing(personId); - personHandler.ifPresent(modules::add); - } - }); - } - modules.forEach(module -> { - Channel channel = module.getThing().getChannel(CHANNEL_WELCOME_HOME_EVENT); - if (channel != null) { - triggerChannel(channel.getUID(), event.getEventType().toString()); - } - }); - } - } - - private @Nullable String getWebHookURI() { - String webHookURI = null; - WelcomeWebHookServlet webHookServlet = this.webHookServlet; - if (configuration.webHookUrl != null && (configuration.readWelcome || configuration.readPresence) - && webHookServlet != null) { - webHookURI = configuration.webHookUrl + webHookServlet.getPath(); - } - return webHookURI; - } - - public boolean registerDataListener(NetatmoDataListener dataListener) { - return dataListeners.add(dataListener); - } - - public boolean unregisterDataListener(NetatmoDataListener dataListener) { - return dataListeners.remove(dataListener); - } - - public void checkForNewThings(Object data) { - for (NetatmoDataListener dataListener : dataListeners) { - dataListener.onDataRefreshed(data); - } - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PersonHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PersonHandler.java index afeb85a17ffa4..080285cabf0f7 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PersonHandler.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PersonHandler.java @@ -68,7 +68,7 @@ public void initialize() { HomeSecurityHandler homeHandler = getHomeHandler(); if (homeHandler != null) { List lastEvents = homeHandler.getLastEventOf(config.id); - if (lastEvents.size() > 0) { + if (!lastEvents.isEmpty()) { setEvent(lastEvents.get(0)); } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookEvent.java index c12f2fe76903f..c472ed1427afd 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookEvent.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookEvent.java @@ -59,7 +59,7 @@ public long getTime() { @Override public @Nullable String getPersonId() { - if (persons.size() > 0) { + if (!persons.isEmpty()) { return persons.keySet().iterator().next(); } return null; diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/binding/binding.xml index dbfc8d7a1f071..6a0f490e7f00f 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/binding/binding.xml @@ -1,56 +1,52 @@ - - Netatmo Binding - The Netatmo binding integrates Weather Station with companion modules, Healthy Home Coach, Thermostat Plug - and Welcome Camera. - - -<<<<<<< Upstream, based on origin/main - -======= - - - Client ID provided for the application you created on http://dev.netatmo.com/createapp - - - - - Client Secret provided for the application you created - password - - - - - Your Netatmo API username (email) - - - - - Your Netatmo API password - password - - - - - Protocol, public IP and port to access OH2 server from Internet. - - - - - The reconnection interval to Netatmo API (in s). - 5400 - - - ->>>>>>> 54453ef Rebase and spotless apply - - If set to true, the device and its associated modules are updated in the discovery inbox at each API - call run to refresh device data. Default is false. - false - - + 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"> + + Netatmo Binding + The Netatmo binding integrates Weather Station with companion modules, Healthy Home Coach, Thermostat Plug + and Welcome Camera. + + + + + Client ID provided for the application you created on http://dev.netatmo.com/createapp + + + + + Client Secret provided for the application you created + password + + + + + Your Netatmo API username (email) + + + + + Your Netatmo API password + password + + + + + Protocol, public IP and port to access OH2 server from Internet. + + + + + The reconnection interval to Netatmo API (in s). + 5400 + + + + + If set to true, the device and its associated modules are updated in the discovery inbox at each API + call run to refresh device data. Default is false. + false + + diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/healthyhomecoach.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/healthyhomecoach.xml deleted file mode 100644 index 1d4a4ed0b0b7b..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/healthyhomecoach.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - This represents the healthy home coach capable of reporting health - index,temperature,humidity,pressure,air quality and sound level - - - - - - - - - - - - - - - - - - - - - - - 99,84,69,54 - auto - - - id - - - diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/station.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/station.xml deleted file mode 100644 index aa19c9ddcb429..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/station.xml +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - - - - - This represents the main indoor module capable of reporting temperature,humidity,pressure,air quality and - sound level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 99,84,69,54 - auto - - - id - - - - - - - - - - This represents the outdoor module capable of reporting temperature and humidity - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 90,80,70,60 - 3600,4500,6000 - - - id - - - - - - - - - - This represents the wind module capable of reporting wind angle and strength - - - - - - - - - - - - - - - - - - 90,80,70,60 - 3950,4770,6000 - - - id - - - - - - - - - - This represents the Rain Gauge capable of measuring precipitation - - - - - - - - - - - - - - - - - 90,80,70,60 - 3600,4500,6000 - - - id - - - - - - - - - - This represents an additional indoor module capable of reporting temperature, humidity and CO2 level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 90,80,70,60 - 4200,4920,6000 - - - id - - - - diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/thermostat.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/thermostat.xml deleted file mode 100644 index b998b8738cde3..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/thermostat.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - This represents the thermostat relay - - - - - - - - - - - - - 99,84,69,54 - 3600000 - - - id - - - - - - - - - - This represents the thermostat module itself - - - - - - - - - - - - - - - - - - - - 90,80,70,60 - 2700,3300,4500 - - - id - - - -