mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[Netatmo] Binding rewrite without external dependencies - step 3 (#12357)
Signed-off-by: clinique <gael@lhopital.org> Also-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
parent
3b793a73a1
commit
adda4c8769
@ -11,40 +11,3 @@ https://www.eclipse.org/legal/epl-2.0/.
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
commons-codec
|
||||
* License: Apache 2.0 License
|
||||
* Project; https://commons.apache.org/proper/commons-codec
|
||||
* Source: https://commons.apache.org/proper/commons-codec
|
||||
|
||||
gson-fire
|
||||
* License: Apache 2.0 License
|
||||
* Project: http://gsonfire.io
|
||||
* Source: https://github.com/julman99/gson-fire
|
||||
|
||||
json
|
||||
* License: JSON License
|
||||
* Project: https://www.json.org
|
||||
* Source: https://github.com/douglascrockford/JSON-java
|
||||
|
||||
okhttp
|
||||
* License: Apache 2.0 License
|
||||
* Project: https://square.github.io/okhttp
|
||||
* Source: https://github.com/square/okhttp
|
||||
|
||||
okio
|
||||
* License: Apache 2.0 License
|
||||
* Project: https://square.github.io/okio/2.x/okio/jvm/okio
|
||||
* Source: https://github.com/square/okio
|
||||
|
||||
oltu.oauth2
|
||||
* License: Apache 2.0 License
|
||||
* Project: https://oltu.apache.org
|
||||
* Source: https://svn.apache.org/viewvc/oltu/trunk
|
||||
|
||||
netatmo-swagger-decl
|
||||
* License: MIT License
|
||||
* Project: https://dev.netatmo.com
|
||||
* Source: https://github.com/cbornet/netatmo-swagger-decl
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,114 +14,4 @@
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Netatmo Binding</name>
|
||||
|
||||
<properties>
|
||||
<bnd.importpackage>!android.*,!com.android.org.*,!org.apache.harmony.*,!sun.*,!org.apache.oltu.*</bnd.importpackage>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.osgiify</groupId>
|
||||
<artifactId>org.json.json</artifactId>
|
||||
<version>20131018</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>2.7.5</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.android</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>logging-interceptor</artifactId>
|
||||
<version>2.7.5</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.android</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okio</groupId>
|
||||
<artifactId>okio</artifactId>
|
||||
<version>1.6.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.gsonfire</groupId>
|
||||
<artifactId>gson-fire</artifactId>
|
||||
<version>1.8.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.oltu.oauth2</groupId>
|
||||
<artifactId>org.apache.oltu.oauth2.client</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.oltu.oauth2</groupId>
|
||||
<artifactId>org.apache.oltu.oauth2.common</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.8</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.8.5</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.swagger.codegen.v3</groupId>
|
||||
<artifactId>swagger-codegen-maven-plugin</artifactId>
|
||||
<version>3.0.21</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<inputSpec>https://raw.githubusercontent.com/cbornet/netatmo-swagger-decl/35e27745fb0d432bc6c8b5ec7a83ed2a09944cea/spec/swagger.yaml</inputSpec>
|
||||
<language>java</language>
|
||||
<generateApiTests>false</generateApiTests>
|
||||
<generateModelTests>false</generateModelTests>
|
||||
<configOptions>
|
||||
<sourceFolder>src/main/java</sourceFolder>
|
||||
<java8>true</java8>
|
||||
<dateLibrary>java8-localdatetime</dateLibrary>
|
||||
<useRuntimeException>true</useRuntimeException>
|
||||
</configOptions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<!-- Required for JDK 17 compatibility, see: https://github.com/swagger-api/swagger-codegen/issues/11253 -->
|
||||
<groupId>com.github.jknack</groupId>
|
||||
<artifactId>handlebars</artifactId>
|
||||
<version>4.3.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
<feature name="openhab-binding-netatmo" description="Netatmo Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle dependency="true">mvn:org.openhab.osgiify/org.json.json/20131018</bundle>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.netatmo/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
|
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link APIUtils} provides util methods for the usage of the generated API classes.
|
||||
*
|
||||
* @author Sven Strohschein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class APIUtils {
|
||||
|
||||
private APIUtils() {
|
||||
}
|
||||
|
||||
public static <T> Stream<T> nonNullStream(Collection<T> collection) {
|
||||
return Optional.ofNullable(collection).stream().flatMap(Collection::stream);
|
||||
}
|
||||
|
||||
public static <T> List<T> nonNullList(List<T> list) {
|
||||
return Optional.ofNullable(list).orElse(Collections.emptyList());
|
||||
}
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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;
|
||||
}
|
||||
}
|
@ -12,292 +12,127 @@
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent.EventTypeEnum;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link NetatmoBinding} class defines common constants, which are used
|
||||
* The {@link NetatmoBindingConstants} class defines common constants, which are used
|
||||
* across the whole binding.
|
||||
*
|
||||
* @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 NetatmoBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "netatmo";
|
||||
|
||||
public static final String BINDING_ID = "netatmo";
|
||||
public static final String VENDOR = "Netatmo";
|
||||
|
||||
// Configuration keys
|
||||
public static final String EQUIPMENT_ID = "id";
|
||||
public static final String PARENT_ID = "parentId";
|
||||
public static final String REFRESH_INTERVAL = "refreshInterval";
|
||||
public static final String SETPOINT_DEFAULT_DURATION = "setpointDefaultDuration";
|
||||
|
||||
public static final String WEBHOOK_APP = "app_security";
|
||||
// Things properties
|
||||
public static final String PROPERTY_CITY = "city";
|
||||
public static final String PROPERTY_COUNTRY = "country";
|
||||
public static final String PROPERTY_TIMEZONE = "timezone";
|
||||
public static final String PROPERTY_FEATURE = "feature";
|
||||
|
||||
// Scale for Weather Station /getmeasure
|
||||
public static final String THIRTY_MINUTES = "30min";
|
||||
public static final String ONE_HOUR = "1hour";
|
||||
public static final String THREE_HOURS = "3hours";
|
||||
public static final String ONE_DAY = "1day";
|
||||
public static final String ONE_WEEK = "1week";
|
||||
public static final String ONE_MONTH = "1month";
|
||||
// Channel group ids
|
||||
public static final String GROUP_LAST_EVENT = "last-event";
|
||||
public static final String GROUP_TEMPERATURE = "temperature";
|
||||
public static final String GROUP_HUMIDITY = "humidity";
|
||||
public static final String GROUP_AIR_QUALITY = "airquality";
|
||||
public static final String GROUP_NOISE = "noise";
|
||||
public static final String GROUP_PRESSURE = "pressure";
|
||||
public static final String GROUP_TIMESTAMP = "timestamp";
|
||||
public static final String GROUP_RAIN = "rain";
|
||||
public static final String GROUP_WIND = "wind";
|
||||
public static final String GROUP_ENERGY = "energy";
|
||||
public static final String GROUP_SIGNAL = "signal";
|
||||
public static final String GROUP_BATTERY = "battery";
|
||||
public static final String GROUP_SECURITY = "security";
|
||||
public static final String GROUP_CAM_STATUS = "status";
|
||||
public static final String GROUP_CAM_LIVE = "live";
|
||||
public static final String GROUP_PRESENCE = "presence";
|
||||
public static final String GROUP_PERSON = "person";
|
||||
public static final String GROUP_PERSON_EVENT = "person-event";
|
||||
public static final String GROUP_ROOM_TEMPERATURE = "room-temperature";
|
||||
public static final String GROUP_ROOM_PROPERTIES = "room-properties";
|
||||
public static final String GROUP_TH_PROPERTIES = "th-properties";
|
||||
public static final String GROUP_TH_SETPOINT = "setpoint";
|
||||
public static final String GROUP_LOCATION = "location";
|
||||
|
||||
// Type for Weather Station /getmeasure
|
||||
public static final String DATE_MIN_CO2 = "date_min_co2";
|
||||
public static final String DATE_MAX_CO2 = "date_max_co2";
|
||||
public static final String DATE_MIN_HUM = "date_min_hum";
|
||||
public static final String DATE_MAX_HUM = "date_max_hum";
|
||||
public static final String DATE_MIN_NOISE = "date_min_noise";
|
||||
public static final String DATE_MAX_NOISE = "date_max_noise";
|
||||
public static final String DATE_MIN_PRESSURE = "date_min_pressure";
|
||||
public static final String DATE_MAX_PRESSURE = "date_max_pressure";
|
||||
public static final String DATE_MIN_TEMP = "date_min_temp";
|
||||
public static final String DATE_MAX_TEMP = "date_max_temp";
|
||||
public static final String MIN_CO2 = "min_co2";
|
||||
public static final String MAX_CO2 = "max_co2";
|
||||
public static final String MIN_HUM = "min_hum";
|
||||
public static final String MAX_HUM = "max_hum";
|
||||
public static final String MIN_NOISE = "min_noise";
|
||||
public static final String MAX_NOISE = "max_noise";
|
||||
public static final String MIN_PRESSURE = "min_pressure";
|
||||
public static final String MAX_PRESSURE = "max_pressure";
|
||||
public static final String MIN_TEMP = "min_temp";
|
||||
public static final String MAX_TEMP = "max_temp";
|
||||
public static final String SUM_RAIN = "sum_rain";
|
||||
// Alternative extended groups
|
||||
public static final String OPTION_EXTENDED = "-extended";
|
||||
public static final String OPTION_OUTSIDE = "-outside";
|
||||
public static final String GROUP_TYPE_TIMESTAMP_EXTENDED = GROUP_TIMESTAMP + OPTION_EXTENDED;
|
||||
public static final String GROUP_TYPE_BATTERY_EXTENDED = GROUP_BATTERY + OPTION_EXTENDED;
|
||||
public static final String GROUP_TYPE_PRESSURE_EXTENDED = GROUP_PRESSURE + OPTION_EXTENDED;
|
||||
public static final String GROUP_TYPE_TEMPERATURE_EXTENDED = GROUP_TEMPERATURE + OPTION_EXTENDED;
|
||||
public static final String GROUP_TYPE_AIR_QUALITY_EXTENDED = GROUP_AIR_QUALITY + OPTION_EXTENDED;
|
||||
public static final String GROUP_TYPE_TEMPERATURE_OUTSIDE = GROUP_TEMPERATURE + OPTION_OUTSIDE;
|
||||
|
||||
// List of Bridge Type UIDs
|
||||
public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "netatmoapi");
|
||||
|
||||
// List of Weather Station Things Type UIDs
|
||||
public static final ThingTypeUID MAIN_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAMain");
|
||||
public static final ThingTypeUID MODULE1_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule1");
|
||||
public static final ThingTypeUID MODULE2_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule2");
|
||||
public static final ThingTypeUID MODULE3_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule3");
|
||||
public static final ThingTypeUID MODULE4_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule4");
|
||||
|
||||
// Netatmo Health Coach
|
||||
public static final ThingTypeUID HOMECOACH_THING_TYPE = new ThingTypeUID(BINDING_ID, "NHC");
|
||||
|
||||
// List of Thermostat Things Type UIDs
|
||||
public static final ThingTypeUID PLUG_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAPlug");
|
||||
public static final ThingTypeUID THERM1_THING_TYPE = new ThingTypeUID(BINDING_ID, "NATherm1");
|
||||
|
||||
// List of Welcome Home Things Type UIDs
|
||||
public static final ThingTypeUID WELCOME_HOME_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAWelcomeHome");
|
||||
public static final ThingTypeUID WELCOME_CAMERA_THING_TYPE = new ThingTypeUID(BINDING_ID, "NACamera");
|
||||
public static final ThingTypeUID WELCOME_PERSON_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAWelcomePerson");
|
||||
// Presence camera
|
||||
public static final ThingTypeUID PRESENCE_CAMERA_THING_TYPE = new ThingTypeUID(BINDING_ID, "NOC");
|
||||
|
||||
// Weather Station Channel ids
|
||||
public static final String CHANNEL_TEMPERATURE = "Temperature";
|
||||
public static final String CHANNEL_TEMP_TREND = "TempTrend";
|
||||
public static final String CHANNEL_HUMIDITY = "Humidity";
|
||||
public static final String CHANNEL_MAX_HUMIDITY = "MaxHumidity";
|
||||
public static final String CHANNEL_MAX_HUMIDITY_THIS_WEEK = "MaxHumidityThisWeek";
|
||||
public static final String CHANNEL_MAX_HUMIDITY_THIS_MONTH = "MaxHumidityThisMonth";
|
||||
public static final String CHANNEL_MIN_HUMIDITY = "MinHumidity";
|
||||
public static final String CHANNEL_MIN_HUMIDITY_THIS_WEEK = "MinHumidityThisWeek";
|
||||
public static final String CHANNEL_MIN_HUMIDITY_THIS_MONTH = "MinHumidityThisMonth";
|
||||
public static final String CHANNEL_HUMIDEX = "Humidex";
|
||||
public static final String CHANNEL_TIMEUTC = "TimeStamp";
|
||||
public static final String CHANNEL_DEWPOINT = "Dewpoint";
|
||||
public static final String CHANNEL_DEWPOINTDEP = "DewpointDepression";
|
||||
public static final String CHANNEL_HEATINDEX = "HeatIndex";
|
||||
public static final String CHANNEL_LAST_STATUS_STORE = "LastStatusStore";
|
||||
public static final String CHANNEL_LAST_MESSAGE = "LastMessage";
|
||||
public static final String CHANNEL_LOCATION = "Location";
|
||||
public static final String CHANNEL_DATE_MAX_CO2 = "DateMaxCo2";
|
||||
public static final String CHANNEL_DATE_MAX_CO2_THIS_WEEK = "DateMaxCo2ThisWeek";
|
||||
public static final String CHANNEL_DATE_MAX_CO2_THIS_MONTH = "DateMaxCo2ThisMonth";
|
||||
public static final String CHANNEL_DATE_MIN_CO2 = "DateMinCo2";
|
||||
public static final String CHANNEL_DATE_MIN_CO2_THIS_WEEK = "DateMinCo2ThisWeek";
|
||||
public static final String CHANNEL_DATE_MIN_CO2_THIS_MONTH = "DateMinCo2ThisMonth";
|
||||
public static final String CHANNEL_DATE_MAX_HUMIDITY = "DateMaxHumidity";
|
||||
public static final String CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK = "DateMaxHumidityThisWeek";
|
||||
public static final String CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH = "DateMaxHumidityThisMonth";
|
||||
public static final String CHANNEL_DATE_MIN_HUMIDITY = "DateMinHumidity";
|
||||
public static final String CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK = "DateMinHumidityThisWeek";
|
||||
public static final String CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH = "DateMinHumidityThisMonth";
|
||||
public static final String CHANNEL_DATE_MAX_NOISE = "DateMaxNoise";
|
||||
public static final String CHANNEL_DATE_MAX_NOISE_THIS_WEEK = "DateMaxNoiseThisWeek";
|
||||
public static final String CHANNEL_DATE_MAX_NOISE_THIS_MONTH = "DateMaxNoiseThisMonth";
|
||||
public static final String CHANNEL_DATE_MIN_NOISE = "DateMinNoise";
|
||||
public static final String CHANNEL_DATE_MIN_NOISE_THIS_WEEK = "DateMinNoiseThisWeek";
|
||||
public static final String CHANNEL_DATE_MIN_NOISE_THIS_MONTH = "DateMinNoiseThisMonth";
|
||||
public static final String CHANNEL_DATE_MAX_PRESSURE = "DateMaxPressure";
|
||||
public static final String CHANNEL_DATE_MAX_PRESSURE_THIS_WEEK = "DateMaxPressureThisWeek";
|
||||
public static final String CHANNEL_DATE_MAX_PRESSURE_THIS_MONTH = "DateMaxPressureThisMonth";
|
||||
public static final String CHANNEL_DATE_MIN_PRESSURE = "DateMinPressure";
|
||||
public static final String CHANNEL_DATE_MIN_PRESSURE_THIS_WEEK = "DateMinPressureThisWeek";
|
||||
public static final String CHANNEL_DATE_MIN_PRESSURE_THIS_MONTH = "DateMinPressureThisMonth";
|
||||
public static final String CHANNEL_DATE_MAX_TEMP = "DateMaxTemp";
|
||||
public static final String CHANNEL_DATE_MAX_TEMP_THIS_WEEK = "DateMaxTempThisWeek";
|
||||
public static final String CHANNEL_DATE_MAX_TEMP_THIS_MONTH = "DateMaxTempThisMonth";
|
||||
public static final String CHANNEL_DATE_MIN_TEMP = "DateMinTemp";
|
||||
public static final String CHANNEL_DATE_MIN_TEMP_THIS_WEEK = "DateMinTempThisWeek";
|
||||
public static final String CHANNEL_DATE_MIN_TEMP_THIS_MONTH = "DateMinTempThisMonth";
|
||||
public static final String CHANNEL_MAX_TEMP = "MaxTemp";
|
||||
public static final String CHANNEL_MAX_TEMP_THIS_WEEK = "MaxTempThisWeek";
|
||||
public static final String CHANNEL_MAX_TEMP_THIS_MONTH = "MaxTempThisMonth";
|
||||
public static final String CHANNEL_MIN_TEMP = "MinTemp";
|
||||
public static final String CHANNEL_MIN_TEMP_THIS_WEEK = "MinTempThisWeek";
|
||||
public static final String CHANNEL_MIN_TEMP_THIS_MONTH = "MinTempThisMonth";
|
||||
public static final String CHANNEL_ABSOLUTE_PRESSURE = "AbsolutePressure";
|
||||
public static final String CHANNEL_CO2 = "Co2";
|
||||
public static final String CHANNEL_MAX_CO2 = "MaxCo2";
|
||||
public static final String CHANNEL_MAX_CO2_THIS_WEEK = "MaxCo2ThisWeek";
|
||||
public static final String CHANNEL_MAX_CO2_THIS_MONTH = "MaxCo2ThisMonth";
|
||||
public static final String CHANNEL_MIN_CO2 = "MinCo2";
|
||||
public static final String CHANNEL_MIN_CO2_THIS_WEEK = "MinCo2ThisWeek";
|
||||
public static final String CHANNEL_MIN_CO2_THIS_MONTH = "MinCo2ThisMonth";
|
||||
public static final String CHANNEL_NOISE = "Noise";
|
||||
public static final String CHANNEL_MAX_NOISE = "MaxNoise";
|
||||
public static final String CHANNEL_MAX_NOISE_THIS_WEEK = "MaxNoiseThisWeek";
|
||||
public static final String CHANNEL_MAX_NOISE_THIS_MONTH = "MaxNoiseThisMonth";
|
||||
public static final String CHANNEL_MIN_NOISE = "MinNoise";
|
||||
public static final String CHANNEL_MIN_NOISE_THIS_WEEK = "MinNoiseThisWeek";
|
||||
public static final String CHANNEL_MIN_NOISE_THIS_MONTH = "MinNoiseThisMonth";
|
||||
public static final String CHANNEL_PRESSURE = "Pressure";
|
||||
public static final String CHANNEL_MAX_PRESSURE = "MaxPressure";
|
||||
public static final String CHANNEL_MAX_PRESSURE_THIS_WEEK = "MaxPressureThisWeek";
|
||||
public static final String CHANNEL_MAX_PRESSURE_THIS_MONTH = "MaxPressureThisMonth";
|
||||
public static final String CHANNEL_MIN_PRESSURE = "MinPressure";
|
||||
public static final String CHANNEL_MIN_PRESSURE_THIS_WEEK = "MinPressureThisWeek";
|
||||
public static final String CHANNEL_MIN_PRESSURE_THIS_MONTH = "MinPressureThisMonth";
|
||||
public static final String CHANNEL_PRESS_TREND = "PressTrend";
|
||||
public static final String CHANNEL_RAIN = "Rain";
|
||||
public static final String CHANNEL_SUM_RAIN1 = "SumRain1";
|
||||
public static final String CHANNEL_SUM_RAIN24 = "SumRain24";
|
||||
public static final String CHANNEL_SUM_RAIN_THIS_WEEK = "SumRainThisWeek";
|
||||
public static final String CHANNEL_SUM_RAIN_THIS_MONTH = "SumRainThisMonth";
|
||||
public static final String CHANNEL_WIND_ANGLE = "WindAngle";
|
||||
public static final String CHANNEL_WIND_STRENGTH = "WindStrength";
|
||||
public static final String CHANNEL_MAX_WIND_STRENGTH = "MaxWindStrength";
|
||||
public static final String CHANNEL_DATE_MAX_WIND_STRENGTH = "DateMaxWindStrength";
|
||||
public static final String CHANNEL_GUST_ANGLE = "GustAngle";
|
||||
public static final String CHANNEL_GUST_STRENGTH = "GustStrength";
|
||||
public static final String CHANNEL_LOW_BATTERY = "LowBattery";
|
||||
public static final String CHANNEL_BATTERY_LEVEL = "BatteryVP";
|
||||
public static final String CHANNEL_WIFI_STATUS = "WifiStatus";
|
||||
public static final String CHANNEL_RF_STATUS = "RfStatus";
|
||||
|
||||
// Healthy Home Coach specific channel
|
||||
public static final String CHANNEL_HEALTH_INDEX = "HealthIndex";
|
||||
|
||||
// Thermostat specific channels
|
||||
public static final String CHANNEL_SETPOINT_MODE = "SetpointMode";
|
||||
public static final String CHANNEL_SETPOINT_END_TIME = "SetpointEndTime";
|
||||
public static final String CHANNEL_SETPOINT_TEMP = "Sp_Temperature";
|
||||
public static final String CHANNEL_THERM_RELAY = "ThermRelayCmd";
|
||||
public static final String CHANNEL_THERM_ORIENTATION = "ThermOrientation";
|
||||
public static final String CHANNEL_CONNECTED_BOILER = "ConnectedBoiler";
|
||||
public static final String CHANNEL_LAST_PLUG_SEEN = "LastPlugSeen";
|
||||
public static final String CHANNEL_LAST_BILAN = "LastBilan";
|
||||
|
||||
public static final String CHANNEL_PLANNING = "Planning";
|
||||
|
||||
public static final String CHANNEL_SETPOINT_MODE_MANUAL = "manual";
|
||||
public static final String CHANNEL_SETPOINT_MODE_AWAY = "away";
|
||||
public static final String CHANNEL_SETPOINT_MODE_HG = "hg";
|
||||
public static final String CHANNEL_SETPOINT_MODE_OFF = "off";
|
||||
public static final String CHANNEL_SETPOINT_MODE_MAX = "max";
|
||||
public static final String CHANNEL_SETPOINT_MODE_PROGRAM = "program";
|
||||
|
||||
// Module Properties
|
||||
public static final String PROPERTY_SIGNAL_LEVELS = "signalLevels";
|
||||
public static final String PROPERTY_BATTERY_LEVELS = "batteryLevels";
|
||||
public static final String PROPERTY_REFRESH_PERIOD = "refreshPeriod";
|
||||
|
||||
// Welcome Home specific channels
|
||||
public static final String CHANNEL_WELCOME_HOME_CITY = "welcomeHomeCity";
|
||||
public static final String CHANNEL_WELCOME_HOME_COUNTRY = "welcomeHomeCountry";
|
||||
public static final String CHANNEL_WELCOME_HOME_TIMEZONE = "welcomeHomeTimezone";
|
||||
public static final String CHANNEL_WELCOME_HOME_PERSONCOUNT = "welcomeHomePersonCount";
|
||||
public static final String CHANNEL_WELCOME_HOME_UNKNOWNCOUNT = "welcomeHomeUnknownCount";
|
||||
|
||||
public static final String CHANNEL_WELCOME_HOME_EVENT = "welcomeHomeEvent";
|
||||
|
||||
public static final String CHANNEL_CAMERA_EVENT = "cameraEvent";
|
||||
|
||||
public static final String CHANNEL_WELCOME_PERSON_LASTSEEN = "welcomePersonLastSeen";
|
||||
public static final String CHANNEL_WELCOME_PERSON_ATHOME = "welcomePersonAtHome";
|
||||
public static final String CHANNEL_WELCOME_PERSON_AVATAR_URL = "welcomePersonAvatarUrl";
|
||||
public static final String CHANNEL_WELCOME_PERSON_AVATAR = "welcomePersonAvatar";
|
||||
public static final String CHANNEL_WELCOME_PERSON_LASTMESSAGE = "welcomePersonLastEventMessage";
|
||||
public static final String CHANNEL_WELCOME_PERSON_LASTTIME = "welcomePersonLastEventTime";
|
||||
public static final String CHANNEL_WELCOME_PERSON_LASTEVENT = "welcomePersonLastEvent";
|
||||
public static final String CHANNEL_WELCOME_PERSON_LASTEVENT_URL = "welcomePersonLastEventUrl";
|
||||
|
||||
public static final String CHANNEL_WELCOME_CAMERA_STATUS = "welcomeCameraStatus";
|
||||
public static final String CHANNEL_WELCOME_CAMERA_SDSTATUS = "welcomeCameraSdStatus";
|
||||
public static final String CHANNEL_WELCOME_CAMERA_ALIMSTATUS = "welcomeCameraAlimStatus";
|
||||
public static final String CHANNEL_WELCOME_CAMERA_ISLOCAL = "welcomeCameraIsLocal";
|
||||
public static final String CHANNEL_WELCOME_CAMERA_LIVEPICTURE = "welcomeCameraLivePicture";
|
||||
public static final String CHANNEL_WELCOME_CAMERA_LIVEPICTURE_URL = "welcomeCameraLivePictureUrl";
|
||||
public static final String CHANNEL_WELCOME_CAMERA_LIVESTREAM_URL = "welcomeCameraLiveStreamUrl";
|
||||
|
||||
public static final String CHANNEL_WELCOME_EVENT_TYPE = "welcomeEventType";
|
||||
public static final String CHANNEL_WELCOME_EVENT_TIME = "welcomeEventTime";
|
||||
public static final String CHANNEL_WELCOME_EVENT_CAMERAID = "welcomeEventCameraId";
|
||||
public static final String CHANNEL_WELCOME_EVENT_PERSONID = "welcomeEventPersonId";
|
||||
public static final String CHANNEL_WELCOME_EVENT_SNAPSHOT = "welcomeEventSnapshot";
|
||||
public static final String CHANNEL_WELCOME_EVENT_SNAPSHOT_URL = "welcomeEventSnapshotURL";
|
||||
public static final String CHANNEL_WELCOME_EVENT_VIDEO_URL = "welcomeEventVideoURL";
|
||||
public static final String CHANNEL_WELCOME_EVENT_VIDEOSTATUS = "welcomeEventVideoStatus";
|
||||
public static final String CHANNEL_WELCOME_EVENT_ISARRIVAL = "welcomeEventIsArrival";
|
||||
public static final String CHANNEL_WELCOME_EVENT_MESSAGE = "welcomeEventMessage";
|
||||
public static final String CHANNEL_WELCOME_EVENT_SUBTYPE = "welcomeEventSubType";
|
||||
|
||||
// Camera specific channels
|
||||
public static final String CHANNEL_CAMERA_STATUS = "cameraStatus";
|
||||
public static final String CHANNEL_CAMERA_SDSTATUS = "cameraSdStatus";
|
||||
public static final String CHANNEL_CAMERA_ALIMSTATUS = "cameraAlimStatus";
|
||||
public static final String CHANNEL_CAMERA_ISLOCAL = "cameraIsLocal";
|
||||
public static final String CHANNEL_CAMERA_LIVEPICTURE = "cameraLivePicture";
|
||||
public static final String CHANNEL_CAMERA_LIVEPICTURE_URL = "cameraLivePictureUrl";
|
||||
public static final String CHANNEL_CAMERA_LIVESTREAM_URL = "cameraLiveStreamUrl";
|
||||
|
||||
public static final String WELCOME_PICTURE_URL = "https://api.netatmo.com/api/getcamerapicture";
|
||||
public static final String WELCOME_PICTURE_IMAGEID = "image_id";
|
||||
public static final String WELCOME_PICTURE_KEY = "key";
|
||||
|
||||
// Presence outdoor camera specific channels
|
||||
public static final String CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE = "cameraFloodlightAutoMode";
|
||||
public static final String CHANNEL_CAMERA_FLOODLIGHT = "cameraFloodlight";
|
||||
|
||||
// List of all supported physical devices and modules
|
||||
public static final Set<ThingTypeUID> SUPPORTED_DEVICE_THING_TYPES_UIDS = Stream
|
||||
.of(MAIN_THING_TYPE, MODULE1_THING_TYPE, MODULE2_THING_TYPE, MODULE3_THING_TYPE, MODULE4_THING_TYPE,
|
||||
HOMECOACH_THING_TYPE, PLUG_THING_TYPE, THERM1_THING_TYPE, WELCOME_HOME_THING_TYPE,
|
||||
WELCOME_CAMERA_THING_TYPE, WELCOME_PERSON_THING_TYPE, PRESENCE_CAMERA_THING_TYPE)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// List of all adressable things in OH = SUPPORTED_DEVICE_THING_TYPES_UIDS + the virtual bridge
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||
.concat(SUPPORTED_DEVICE_THING_TYPES_UIDS.stream(), Stream.of(APIBRIDGE_THING_TYPE))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
public static final Set<EventTypeEnum> HOME_EVENTS = Stream.of(EventTypeEnum.PERSON_AWAY)
|
||||
.collect(Collectors.toSet());
|
||||
public static final Set<EventTypeEnum> WELCOME_EVENTS = Stream
|
||||
.of(EventTypeEnum.PERSON, EventTypeEnum.MOVEMENT, EventTypeEnum.CONNECTION, EventTypeEnum.DISCONNECTION,
|
||||
EventTypeEnum.ON, EventTypeEnum.OFF, EventTypeEnum.BOOT, EventTypeEnum.SD, EventTypeEnum.ALIM,
|
||||
EventTypeEnum.NEW_MODULE, EventTypeEnum.MODULE_CONNECT, EventTypeEnum.MODULE_DISCONNECT,
|
||||
EventTypeEnum.MODULE_LOW_BATTERY, EventTypeEnum.MODULE_END_UPDATE, EventTypeEnum.TAG_BIG_MOVE,
|
||||
EventTypeEnum.TAG_SMALL_MOVE, EventTypeEnum.TAG_UNINSTALLED, EventTypeEnum.TAG_OPEN)
|
||||
.collect(Collectors.toSet());
|
||||
public static final Set<EventTypeEnum> PERSON_EVENTS = Stream.of(EventTypeEnum.PERSON, EventTypeEnum.PERSON_AWAY)
|
||||
.collect(Collectors.toSet());
|
||||
public static final Set<EventTypeEnum> PRESENCE_EVENTS = Stream
|
||||
.of(EventTypeEnum.OUTDOOR, EventTypeEnum.ALIM, EventTypeEnum.DAILY_SUMMARY).collect(Collectors.toSet());
|
||||
// Channel ids
|
||||
public static final String CHANNEL_VALUE = "value";
|
||||
public static final String CHANNEL_TREND = "trend";
|
||||
public static final String CHANNEL_MAX_TIME = "max-time";
|
||||
public static final String CHANNEL_MIN_TIME = "min-time";
|
||||
public static final String CHANNEL_MAX_VALUE = "max-today";
|
||||
public static final String CHANNEL_MIN_VALUE = "min-today";
|
||||
public static final String CHANNEL_HUMIDEX = "humidex";
|
||||
public static final String CHANNEL_CO2 = "co2";
|
||||
public static final String CHANNEL_HEALTH_INDEX = "health-index";
|
||||
public static final String CHANNEL_HUMIDEX_SCALE = "humidex-scale";
|
||||
public static final String CHANNEL_DEWPOINT = "dewpoint";
|
||||
public static final String CHANNEL_DEWPOINT_DEP = "dewpoint-depression";
|
||||
public static final String CHANNEL_HEAT_INDEX = "heat-index";
|
||||
public static final String CHANNEL_ABSOLUTE_PRESSURE = "absolute";
|
||||
public static final String CHANNEL_LAST_SEEN = "last-seen";
|
||||
public static final String CHANNEL_MEASURES_TIMESTAMP = "measures";
|
||||
public static final String CHANNEL_LOW_BATTERY = "low-battery";
|
||||
public static final String CHANNEL_BATTERY_STATUS = "status";
|
||||
public static final String CHANNEL_SIGNAL_STRENGTH = "strength";
|
||||
public static final String CHANNEL_SUM_RAIN1 = "sum-1";
|
||||
public static final String CHANNEL_SUM_RAIN24 = "sum-24";
|
||||
public static final String CHANNEL_WIND_ANGLE = "angle";
|
||||
public static final String CHANNEL_WIND_STRENGTH = "strength";
|
||||
public static final String CHANNEL_MAX_WIND_STRENGTH = "max-strength";
|
||||
public static final String CHANNEL_DATE_MAX_WIND_STRENGTH = "max-strength-date";
|
||||
public static final String CHANNEL_GUST_ANGLE = "gust-angle";
|
||||
public static final String CHANNEL_GUST_STRENGTH = "gust-strength";
|
||||
public static final String CHANNEL_SETPOINT_MODE = "mode";
|
||||
public static final String CHANNEL_SETPOINT_START_TIME = "start";
|
||||
public static final String CHANNEL_SETPOINT_END_TIME = "end";
|
||||
public static final String CHANNEL_THERM_RELAY = "relay-status";
|
||||
public static final String CHANNEL_ANTICIPATING = "anticipating";
|
||||
public static final String CHANNEL_ROOM_WINDOW_OPEN = "window-open";
|
||||
public static final String CHANNEL_ROOM_HEATING_POWER = "heating-power-request";
|
||||
public static final String CHANNEL_PLANNING = "planning";
|
||||
public static final String CHANNEL_PERSON_COUNT = "person-count";
|
||||
public static final String CHANNEL_UNKNOWN_PERSON_COUNT = "unknown-person-count";
|
||||
public static final String CHANNEL_UNKNOWN_PERSON_PICTURE = "unknown-person-picture";
|
||||
public static final String CHANNEL_MONITORING = "monitoring";
|
||||
public static final String CHANNEL_SD_CARD = "sd-card";
|
||||
public static final String CHANNEL_ALIM_STATUS = "alim";
|
||||
public static final String CHANNEL_LIVEPICTURE = "picture";
|
||||
public static final String CHANNEL_LIVEPICTURE_VPN_URL = "vpn-picture-url";
|
||||
public static final String CHANNEL_LIVEPICTURE_LOCAL_URL = "local-picture-url";
|
||||
public static final String CHANNEL_LIVESTREAM_VPN_URL = "vpn-stream-url";
|
||||
public static final String CHANNEL_LIVESTREAM_LOCAL_URL = "local-stream-url";
|
||||
public static final String CHANNEL_EVENT_TYPE = "type";
|
||||
public static final String CHANNEL_EVENT_SUBTYPE = "subtype";
|
||||
public static final String CHANNEL_EVENT_VIDEO_STATUS = "video-status";
|
||||
public static final String CHANNEL_EVENT_MESSAGE = "message";
|
||||
public static final String CHANNEL_EVENT_TIME = "time";
|
||||
public static final String CHANNEL_EVENT_SNAPSHOT = "snapshot";
|
||||
public static final String CHANNEL_EVENT_SNAPSHOT_URL = "snapshot-url";
|
||||
public static final String CHANNEL_EVENT_VIDEO_VPN_URL = "vpn-video-url";
|
||||
public static final String CHANNEL_EVENT_VIDEO_LOCAL_URL = "local-video-url";
|
||||
public static final String CHANNEL_EVENT_PERSON_ID = "person-id";
|
||||
public static final String CHANNEL_EVENT_CAMERA_ID = "camera-id";
|
||||
public static final String CHANNEL_PERSON_AT_HOME = "at-home";
|
||||
public static final String CHANNEL_PERSON_AVATAR = "avatar";
|
||||
public static final String CHANNEL_PERSON_AVATAR_URL = "avatar-url";
|
||||
public static final String CHANNEL_HOME_EVENT = "home-event";
|
||||
public static final String CHANNEL_SETPOINT_DURATION = "setpoint-duration";
|
||||
public static final String CHANNEL_FLOODLIGHT = "floodlight";
|
||||
}
|
||||
|
@ -12,47 +12,46 @@
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.Dictionary;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.discovery.NetatmoModuleDiscoveryService;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
|
||||
import org.openhab.binding.netatmo.internal.homecoach.NAHealthyHomeCoachHandler;
|
||||
import org.openhab.binding.netatmo.internal.presence.NAPresenceCameraHandler;
|
||||
import org.openhab.binding.netatmo.internal.station.NAMainHandler;
|
||||
import org.openhab.binding.netatmo.internal.station.NAModule1Handler;
|
||||
import org.openhab.binding.netatmo.internal.station.NAModule2Handler;
|
||||
import org.openhab.binding.netatmo.internal.station.NAModule3Handler;
|
||||
import org.openhab.binding.netatmo.internal.station.NAModule4Handler;
|
||||
import org.openhab.binding.netatmo.internal.thermostat.NAPlugHandler;
|
||||
import org.openhab.binding.netatmo.internal.thermostat.NATherm1Handler;
|
||||
import org.openhab.binding.netatmo.internal.webhook.WelcomeWebHookServlet;
|
||||
import org.openhab.binding.netatmo.internal.welcome.NAWelcomeCameraHandler;
|
||||
import org.openhab.binding.netatmo.internal.welcome.NAWelcomeHomeHandler;
|
||||
import org.openhab.binding.netatmo.internal.welcome.NAWelcomePersonHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
import org.openhab.binding.netatmo.internal.config.BindingConfiguration;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NADeserializer;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.handler.DeviceHandler;
|
||||
import org.openhab.binding.netatmo.internal.handler.ModuleHandler;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.AirCareCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.CameraCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.Capability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.RoomCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
|
||||
import org.openhab.core.config.core.ConfigParser;
|
||||
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.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Modified;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.slf4j.Logger;
|
||||
@ -68,131 +67,91 @@ import org.slf4j.LoggerFactory;
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.netatmo")
|
||||
public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final Logger logger = LoggerFactory.getLogger(NetatmoHandlerFactory.class);
|
||||
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
private final Map<ThingUID, ServiceRegistration<?>> webHookServiceRegs = new HashMap<>();
|
||||
|
||||
private final NetatmoDescriptionProvider stateDescriptionProvider;
|
||||
private final HttpClient httpClient;
|
||||
private final NADeserializer deserializer;
|
||||
private final HttpService httpService;
|
||||
private final NATherm1StateDescriptionProvider stateDescriptionProvider;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final LocaleProvider localeProvider;
|
||||
private final TranslationProvider translationProvider;
|
||||
private boolean backgroundDiscovery;
|
||||
private final BindingConfiguration configuration = new BindingConfiguration();
|
||||
|
||||
@Activate
|
||||
public NetatmoHandlerFactory(final @Reference HttpService httpService,
|
||||
final @Reference NATherm1StateDescriptionProvider stateDescriptionProvider,
|
||||
final @Reference TimeZoneProvider timeZoneProvider, final @Reference LocaleProvider localeProvider,
|
||||
final @Reference TranslationProvider translationProvider) {
|
||||
this.httpService = httpService;
|
||||
public NetatmoHandlerFactory(@Reference NetatmoDescriptionProvider stateDescriptionProvider,
|
||||
@Reference HttpClientFactory factory, @Reference NADeserializer deserializer,
|
||||
@Reference HttpService httpService, Map<String, @Nullable Object> config) {
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.localeProvider = localeProvider;
|
||||
this.translationProvider = translationProvider;
|
||||
this.httpClient = factory.getCommonHttpClient();
|
||||
this.httpService = httpService;
|
||||
this.deserializer = deserializer;
|
||||
configChanged(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate(ComponentContext componentContext) {
|
||||
super.activate(componentContext);
|
||||
Dictionary<String, Object> properties = componentContext.getProperties();
|
||||
Object property = properties.get("backgroundDiscovery");
|
||||
if (property instanceof Boolean) {
|
||||
backgroundDiscovery = ((Boolean) property).booleanValue();
|
||||
} else {
|
||||
backgroundDiscovery = false;
|
||||
@Modified
|
||||
public void configChanged(Map<String, @Nullable Object> config) {
|
||||
BindingConfiguration newConf = ConfigParser.configurationAs(config, BindingConfiguration.class);
|
||||
if (newConf != null) {
|
||||
configuration.update(newConf);
|
||||
}
|
||||
logger.debug("backgroundDiscovery {}", backgroundDiscovery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID));
|
||||
return ModuleType.AS_SET.stream().anyMatch(mt -> mt.thingTypeUID.equals(thingTypeUID));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (thingTypeUID.equals(APIBRIDGE_THING_TYPE)) {
|
||||
WelcomeWebHookServlet servlet = registerWebHookServlet(thing.getUID());
|
||||
NetatmoBridgeHandler bridgeHandler = new NetatmoBridgeHandler((Bridge) thing, servlet);
|
||||
registerDeviceDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
} else if (thingTypeUID.equals(MODULE1_THING_TYPE)) {
|
||||
return new NAModule1Handler(thing, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(MODULE2_THING_TYPE)) {
|
||||
return new NAModule2Handler(thing, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(MODULE3_THING_TYPE)) {
|
||||
return new NAModule3Handler(thing, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(MODULE4_THING_TYPE)) {
|
||||
return new NAModule4Handler(thing, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(MAIN_THING_TYPE)) {
|
||||
return new NAMainHandler(thing, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(HOMECOACH_THING_TYPE)) {
|
||||
return new NAHealthyHomeCoachHandler(thing, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(PLUG_THING_TYPE)) {
|
||||
return new NAPlugHandler(thing, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(THERM1_THING_TYPE)) {
|
||||
return new NATherm1Handler(thing, stateDescriptionProvider, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(WELCOME_HOME_THING_TYPE)) {
|
||||
return new NAWelcomeHomeHandler(thing, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(WELCOME_CAMERA_THING_TYPE)) {
|
||||
return new NAWelcomeCameraHandler(thing, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(PRESENCE_CAMERA_THING_TYPE)) {
|
||||
return new NAPresenceCameraHandler(thing, timeZoneProvider);
|
||||
} else if (thingTypeUID.equals(WELCOME_PERSON_THING_TYPE)) {
|
||||
return new NAWelcomePersonHandler(thing, timeZoneProvider);
|
||||
} else {
|
||||
logger.warn("ThingHandler not found for {}", thing.getThingTypeUID());
|
||||
return null;
|
||||
}
|
||||
return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst()
|
||||
.map(mt -> buildHandler(thing, mt)).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof NetatmoBridgeHandler) {
|
||||
ThingUID thingUID = thingHandler.getThing().getUID();
|
||||
unregisterDeviceDiscoveryService(thingUID);
|
||||
unregisterWebHookServlet(thingUID);
|
||||
private BaseThingHandler buildHandler(Thing thing, ModuleType moduleType) {
|
||||
if (ModuleType.ACCOUNT.equals(moduleType)) {
|
||||
return new ApiBridgeHandler((Bridge) thing, httpClient, httpService, deserializer, configuration);
|
||||
}
|
||||
}
|
||||
CommonInterface handler = moduleType.isABridge() ? new DeviceHandler((Bridge) thing) : new ModuleHandler(thing);
|
||||
|
||||
private synchronized void registerDeviceDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler) {
|
||||
if (bundleContext != null) {
|
||||
NetatmoModuleDiscoveryService discoveryService = new NetatmoModuleDiscoveryService(netatmoBridgeHandler,
|
||||
localeProvider, translationProvider);
|
||||
Map<String, Object> configProperties = new HashMap<>();
|
||||
configProperties.put(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY,
|
||||
Boolean.valueOf(backgroundDiscovery));
|
||||
discoveryService.activate(configProperties);
|
||||
discoveryServiceRegs.put(netatmoBridgeHandler.getThing().getUID(), bundleContext
|
||||
.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void unregisterDeviceDiscoveryService(ThingUID thingUID) {
|
||||
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(thingUID);
|
||||
if (serviceReg != null) {
|
||||
NetatmoModuleDiscoveryService service = (NetatmoModuleDiscoveryService) bundleContext
|
||||
.getService(serviceReg.getReference());
|
||||
serviceReg.unregister();
|
||||
if (service != null) {
|
||||
service.deactivate();
|
||||
List<ChannelHelper> helpers = new ArrayList<>();
|
||||
moduleType.channelHelpers.forEach(helperClass -> {
|
||||
try {
|
||||
helpers.add(helperClass.getConstructor().newInstance());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
logger.warn("Error creating or initializing helper class : {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
private synchronized @Nullable WelcomeWebHookServlet registerWebHookServlet(ThingUID thingUID) {
|
||||
WelcomeWebHookServlet servlet = null;
|
||||
if (bundleContext != null) {
|
||||
servlet = new WelcomeWebHookServlet(httpService, thingUID.getId());
|
||||
webHookServiceRegs.put(thingUID,
|
||||
bundleContext.registerService(HttpServlet.class.getName(), servlet, new Hashtable<>()));
|
||||
}
|
||||
return servlet;
|
||||
}
|
||||
moduleType.capabilities.forEach(capability -> {
|
||||
Capability newCap = null;
|
||||
if (capability == DeviceCapability.class) {
|
||||
newCap = new DeviceCapability(handler);
|
||||
} else if (capability == AirCareCapability.class) {
|
||||
newCap = new AirCareCapability(handler);
|
||||
} else if (capability == EventCapability.class) {
|
||||
newCap = new EventCapability(handler);
|
||||
} else if (capability == HomeCapability.class) {
|
||||
newCap = new HomeCapability(handler, stateDescriptionProvider);
|
||||
} else if (capability == WeatherCapability.class) {
|
||||
newCap = new WeatherCapability(handler);
|
||||
} else if (capability == RoomCapability.class) {
|
||||
newCap = new RoomCapability(handler);
|
||||
} else if (capability == PersonCapability.class) {
|
||||
newCap = new PersonCapability(handler, stateDescriptionProvider, helpers);
|
||||
} else if (capability == CameraCapability.class) {
|
||||
newCap = new CameraCapability(handler, stateDescriptionProvider, helpers);
|
||||
} else if (capability == PresenceCapability.class) {
|
||||
newCap = new PresenceCapability(handler, stateDescriptionProvider, helpers);
|
||||
} else if (capability == MeasureCapability.class) {
|
||||
newCap = new MeasureCapability(handler, helpers);
|
||||
} else if (capability == ChannelHelperCapability.class) {
|
||||
newCap = new ChannelHelperCapability(handler, helpers);
|
||||
}
|
||||
if (newCap != null) {
|
||||
handler.getCapabilities().put(newCap);
|
||||
} else {
|
||||
logger.warn("No factory entry defined to create Capability : {}", capability);
|
||||
}
|
||||
});
|
||||
|
||||
private synchronized void unregisterWebHookServlet(ThingUID thingUID) {
|
||||
ServiceRegistration<?> serviceReg = webHookServiceRegs.remove(thingUID);
|
||||
if (serviceReg != null) {
|
||||
serviceReg.unregister();
|
||||
}
|
||||
return (BaseThingHandler) handler;
|
||||
}
|
||||
}
|
||||
|
@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link RefreshStrategy} is the class used to embed the refreshing
|
||||
* needs calculation for devices
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RefreshStrategy {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(RefreshStrategy.class);
|
||||
|
||||
private static final int DEFAULT_DELAY = 30; // in seconds
|
||||
private static final int SEARCH_REFRESH_INTERVAL = 120; // in seconds
|
||||
private int dataValidityPeriod;
|
||||
private long dataTimeStamp;
|
||||
private boolean searchRefreshInterval;
|
||||
@Nullable
|
||||
private Integer dataTimestamp0;
|
||||
|
||||
// By default we create dataTimeStamp to be outdated
|
||||
// A null or negative value for dataValidityPeriod will trigger an automatic search of the validity period
|
||||
public RefreshStrategy(int dataValidityPeriod) {
|
||||
if (dataValidityPeriod <= 0) {
|
||||
this.dataValidityPeriod = 0;
|
||||
this.searchRefreshInterval = true;
|
||||
logger.debug("Data validity period search...");
|
||||
} else {
|
||||
this.dataValidityPeriod = dataValidityPeriod;
|
||||
this.searchRefreshInterval = false;
|
||||
logger.debug("Data validity period set to {} ms", this.dataValidityPeriod);
|
||||
}
|
||||
expireData();
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public void setDataTimeStamp(Integer dataTimestamp, ZoneId zoneId) {
|
||||
if (searchRefreshInterval) {
|
||||
if (dataTimestamp0 == null) {
|
||||
dataTimestamp0 = dataTimestamp;
|
||||
logger.debug("First data timestamp is {}", dataTimestamp0);
|
||||
} else if (dataTimestamp.intValue() > dataTimestamp0.intValue()) {
|
||||
dataValidityPeriod = (dataTimestamp.intValue() - dataTimestamp0.intValue()) * 1000;
|
||||
searchRefreshInterval = false;
|
||||
logger.debug("Data validity period found : {} ms", this.dataValidityPeriod);
|
||||
} else {
|
||||
logger.debug("Data validity period not yet found - data timestamp unchanged");
|
||||
}
|
||||
}
|
||||
this.dataTimeStamp = ChannelTypeUtils.toZonedDateTime(dataTimestamp, zoneId).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
public long dataAge() {
|
||||
long now = Calendar.getInstance().getTimeInMillis();
|
||||
return now - dataTimeStamp;
|
||||
}
|
||||
|
||||
public boolean isDataOutdated() {
|
||||
return dataAge() >= dataValidityPeriod;
|
||||
}
|
||||
|
||||
public long nextRunDelayInS() {
|
||||
return searchRefreshInterval ? SEARCH_REFRESH_INTERVAL
|
||||
: Math.max(0, (dataValidityPeriod - dataAge())) / 1000 + DEFAULT_DELAY;
|
||||
}
|
||||
|
||||
public void expireData() {
|
||||
ZonedDateTime now = ZonedDateTime.now().minus(this.dataValidityPeriod, ChronoUnit.MILLIS);
|
||||
dataTimeStamp = now.toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
public boolean isSearchingRefreshInterval() {
|
||||
return searchRefreshInterval && dataTimestamp0 != null;
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.action;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.EnergyCapability;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RoomActions} defines thing actions for RoomHandler.
|
||||
*
|
||||
* @author Markus Dillmann - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "netatmo")
|
||||
@NonNullByDefault
|
||||
public class RoomActions implements ThingActions {
|
||||
private final Logger logger = LoggerFactory.getLogger(RoomActions.class);
|
||||
private static final Set<SetpointMode> ALLOWED_MODES = Set.of(SetpointMode.MAX, SetpointMode.MANUAL,
|
||||
SetpointMode.HOME);
|
||||
|
||||
private @Nullable CommonInterface handler;
|
||||
private Optional<EnergyCapability> energy = Optional.empty();
|
||||
|
||||
public RoomActions() {
|
||||
logger.debug("Netatmo RoomActions service created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof CommonInterface) {
|
||||
CommonInterface commonHandler = (CommonInterface) handler;
|
||||
this.handler = commonHandler;
|
||||
energy = commonHandler.getHomeCapability(EnergyCapability.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return (ThingHandler) handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* The setThermpoint room thing action
|
||||
*/
|
||||
@RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
|
||||
public void setThermpoint(
|
||||
@ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp,
|
||||
@ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) {
|
||||
setThermpoint(temp, endTime, "MANUAL");
|
||||
}
|
||||
|
||||
@RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
|
||||
public void seThermpoint(
|
||||
@ActionInput(name = "mode", label = "@text/actionInputModeLabel", description = "@text/actionInputModeDesc") @Nullable String mode,
|
||||
@ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) {
|
||||
setThermpoint(null, endTime, mode);
|
||||
}
|
||||
|
||||
@RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
|
||||
public void setThermpoint(
|
||||
@ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp,
|
||||
@ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime,
|
||||
@ActionInput(name = "mode", label = "@text/actionInputModeLabel", description = "@text/actionInputModeDesc") @Nullable String mode) {
|
||||
CommonInterface roomHandler = handler;
|
||||
if (roomHandler != null) {
|
||||
String roomId = roomHandler.getId();
|
||||
SetpointMode targetMode = SetpointMode.UNKNOWN;
|
||||
Long targetEndTime = endTime;
|
||||
Double targetTemp = temp;
|
||||
if (mode != null) {
|
||||
try {
|
||||
targetMode = SetpointMode.valueOf(mode);
|
||||
if (!ALLOWED_MODES.contains(targetMode)) {
|
||||
logger.info("Mode can only be MAX, HOME or MANUAL for a room");
|
||||
return;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.info("Invalid mode passed : {} - {}", mode, e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (temp != null) {
|
||||
logger.debug("Temperature provided, mode forced to MANUAL.");
|
||||
targetMode = SetpointMode.MANUAL;
|
||||
if (targetEndTime == null) {
|
||||
logger.info("Temperature provided but no endtime given, action ignored");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (SetpointMode.HOME.equals(targetMode)) {
|
||||
targetEndTime = 0L;
|
||||
targetTemp = 0.0;
|
||||
} else {
|
||||
logger.info("mode is required if no temperature setpoint provided");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
double setpointTemp = targetTemp != null ? targetTemp : 0;
|
||||
long setpointEnd = targetEndTime;
|
||||
SetpointMode setpointMode = targetMode;
|
||||
energy.ifPresent(cap -> cap.setRoomThermTemp(roomId, setpointTemp, setpointEnd, setpointMode));
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("Ignoring setRoomThermpoint command due to illegal argument exception: {}",
|
||||
e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.info("Handler not set for room thing actions.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static setThermpoint method for Rules DSL backward compatibility
|
||||
*/
|
||||
public static void setThermpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime,
|
||||
@Nullable String mode) {
|
||||
((RoomActions) actions).setThermpoint(temp, endTime, mode);
|
||||
}
|
||||
|
||||
public static void setThermpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime) {
|
||||
setThermpoint(actions, temp, endTime, null);
|
||||
}
|
||||
|
||||
public static void setThermpoint(ThingActions actions, @Nullable String mode, @Nullable Long endTime) {
|
||||
setThermpoint(actions, null, endTime, mode);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAMain;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAMain.StationDataResponse;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
|
||||
/**
|
||||
* Base class for all Air Care related endpoints
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AircareApi extends RestManager {
|
||||
|
||||
public AircareApi(ApiBridgeHandler apiClient) {
|
||||
super(apiClient, FeatureArea.AIR_CARE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data from Healthy Home Coach Station (measures and device specific data).
|
||||
*
|
||||
* @param deviceId Id of the device you want to retrieve information of (optional)
|
||||
* @return StationDataResponse
|
||||
* @throws NetatmoException If fail to call the API, e.g. server error or deserializing
|
||||
*/
|
||||
public StationDataResponse getHomeCoachData(@Nullable String deviceId) throws NetatmoException {
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMECOACH, PARAM_DEVICEID, deviceId);
|
||||
return get(uriBuilder, StationDataResponse.class);
|
||||
}
|
||||
|
||||
public NAMain getHomeCoach(String deviceId) throws NetatmoException {
|
||||
ListBodyResponse<NAMain> answer = getHomeCoachData(deviceId).getBody();
|
||||
if (answer != null) {
|
||||
NAMain station = answer.getElement(deviceId);
|
||||
if (station != null) {
|
||||
return station;
|
||||
}
|
||||
}
|
||||
throw new NetatmoException("Unexpected answer querying device '%s' : not found.", deviceId);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError;
|
||||
|
||||
/**
|
||||
* The {@link ApiError} models an errored response from API
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ApiError {
|
||||
private class Body {
|
||||
private String message = "";
|
||||
private ServiceError code = ServiceError.UNKNOWN;
|
||||
}
|
||||
|
||||
private Body error = new Body();
|
||||
|
||||
public String getMessage() {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
public ServiceError getCode() {
|
||||
return error.code;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ApiResponse} models a response returned by API call
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ApiResponse<T> {
|
||||
/**
|
||||
* The {@link Ok} models a response that only holds the result of the request sent to the API
|
||||
*/
|
||||
static class Ok extends ApiResponse<String> {
|
||||
private static final String SUCCESS = "ok";
|
||||
|
||||
boolean failed() {
|
||||
return !SUCCESS.equals(getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private String status = "";
|
||||
private @Nullable T body;
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public @Nullable T getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.PATH_OAUTH;
|
||||
import static org.openhab.core.auth.oauth2client.internal.Keyword.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.AccessTokenResponse;
|
||||
import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration.Credentials;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AuthenticationApi} handles oAuth2 authentication and token refreshing
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AuthenticationApi extends RestManager {
|
||||
private static final URI OAUTH_URI = getApiBaseBuilder().path(PATH_OAUTH).build();
|
||||
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Logger logger = LoggerFactory.getLogger(AuthenticationApi.class);
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshTokenJob;
|
||||
private Optional<AccessTokenResponse> tokenResponse = Optional.empty();
|
||||
private String scope = "";
|
||||
|
||||
public AuthenticationApi(ApiBridgeHandler bridge, ScheduledExecutorService scheduler) {
|
||||
super(bridge, FeatureArea.NONE);
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
public void authenticate(Credentials credentials, Set<FeatureArea> features) throws NetatmoException {
|
||||
Set<FeatureArea> requestedFeatures = !features.isEmpty() ? features : FeatureArea.AS_SET;
|
||||
scope = FeatureArea.toScopeString(requestedFeatures);
|
||||
requestToken(credentials.clientId, credentials.clientSecret,
|
||||
Map.of(USERNAME, credentials.username, PASSWORD, credentials.password, SCOPE, scope));
|
||||
}
|
||||
|
||||
private void requestToken(String id, String secret, Map<String, String> entries) throws NetatmoException {
|
||||
Map<String, String> payload = new HashMap<>(entries);
|
||||
payload.putAll(Map.of(GRANT_TYPE, entries.keySet().contains(PASSWORD) ? PASSWORD : REFRESH_TOKEN, CLIENT_ID, id,
|
||||
CLIENT_SECRET, secret));
|
||||
disconnect();
|
||||
AccessTokenResponse response = post(OAUTH_URI, AccessTokenResponse.class, payload);
|
||||
refreshTokenJob = scheduler.schedule(() -> {
|
||||
try {
|
||||
requestToken(id, secret, Map.of(REFRESH_TOKEN, response.getRefreshToken()));
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Unable to refresh access token : {}", e.getMessage());
|
||||
}
|
||||
}, Math.round(response.getExpiresIn() * 0.8), TimeUnit.SECONDS);
|
||||
tokenResponse = Optional.of(response);
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
tokenResponse = Optional.empty();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
ScheduledFuture<?> job = refreshTokenJob;
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
}
|
||||
refreshTokenJob = null;
|
||||
}
|
||||
|
||||
public @Nullable String getAuthorization() {
|
||||
return tokenResponse.map(at -> String.format("Bearer %s", at.getAccessToken())).orElse(null);
|
||||
}
|
||||
|
||||
public boolean matchesScopes(Set<Scope> requiredScopes) {
|
||||
// either we do not require any scope, either connected and all scopes available
|
||||
return requiredScopes.isEmpty()
|
||||
|| (isConnected() && tokenResponse.map(at -> at.getScope().containsAll(requiredScopes)).orElse(false));
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return !tokenResponse.isEmpty();
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link BodyResponse} models a response returned by API call containing
|
||||
* a list of elements.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BodyResponse<T extends NAObject> {
|
||||
@SerializedName(value = "home")
|
||||
private @Nullable T element;
|
||||
|
||||
public @Nullable T getElement() {
|
||||
return element;
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
|
||||
/**
|
||||
* The {@link EnergyApi} handles API endpoints related to Energy feature area
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class EnergyApi extends RestManager {
|
||||
public EnergyApi(ApiBridgeHandler apiClient) {
|
||||
super(apiClient, FeatureArea.ENERGY);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* The method switchSchedule switches the home's schedule to another existing schedule.
|
||||
*
|
||||
* @param homeId The id of home (required)
|
||||
* @param scheduleId The schedule id. It can be found in the getthermstate response, under the keys
|
||||
* therm_program_backup and therm_program. (required)
|
||||
* @throws NetatmoException If fail to call the API, e.g. server error or cannot deserialize the
|
||||
* response body
|
||||
*/
|
||||
public void switchSchedule(String homeId, String scheduleId) throws NetatmoException {
|
||||
UriBuilder uriBuilder = getAppUriBuilder(SUB_PATH_SWITCHSCHEDULE, PARAM_HOMEID, homeId, PARAM_SCHEDULEID,
|
||||
scheduleId);
|
||||
post(uriBuilder, ApiResponse.Ok.class, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This endpoint permits to control the heating of a specific home. A home can be set in 3 differents modes:
|
||||
* "schedule" mode in which the home will follow the user schedule
|
||||
* "away" mode which will put the whole house to away (default is 12° but can be changed by the user in its
|
||||
* settings)
|
||||
* "hg" corresponds to frostguard mode (7° by default)
|
||||
*
|
||||
* @param homeId The id of home (required)
|
||||
* @param mode The mode. (required)
|
||||
* @throws NetatmoCommunicationException when call failed, e.g. server error or cannot deserialize
|
||||
*/
|
||||
public void setThermMode(String homeId, String mode) throws NetatmoException {
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_SETTHERMMODE, PARAM_HOMEID, homeId, PARAM_MODE, mode);
|
||||
post(uriBuilder, ApiResponse.Ok.class, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* The method setThermpoint changes the Thermostat manual temperature setpoint.
|
||||
*
|
||||
* @param homeId The id of home (required)
|
||||
* @param roomId The id of the room (required)
|
||||
* @param mode The mode. (required)
|
||||
* @param endtime For manual or max setpoint_mode, defines when the setpoint expires.
|
||||
* @param temp For manual setpoint_mode, defines the temperature setpoint (in °C)
|
||||
* @throws NetatmoCommunicationException when call failed, e.g. server error or cannot deserialize
|
||||
*/
|
||||
public void setThermpoint(String homeId, String roomId, SetpointMode mode, long endtime, double temp)
|
||||
throws NetatmoException {
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_SETROOMTHERMPOINT, PARAM_HOMEID, homeId, PARAM_ROOMID, roomId,
|
||||
PARAM_MODE, mode.apiDescriptor);
|
||||
if (mode == SetpointMode.MANUAL || mode == SetpointMode.MAX) {
|
||||
uriBuilder.queryParam("endtime", endtime);
|
||||
if (mode == SetpointMode.MANUAL) {
|
||||
uriBuilder.queryParam("temp", temp > THERM_MAX_SETPOINT ? THERM_MAX_SETPOINT : temp);
|
||||
}
|
||||
}
|
||||
post(uriBuilder, ApiResponse.Ok.class, null, null);
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeData;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.NAHomeStatusResponse;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
|
||||
/**
|
||||
* The {@link HomeApi} handles general API endpoints not requiring specific scope area
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class HomeApi extends RestManager {
|
||||
|
||||
public HomeApi(ApiBridgeHandler apiClient) {
|
||||
super(apiClient, FeatureArea.NONE);
|
||||
}
|
||||
|
||||
public @Nullable HomeStatus getHomeStatus(String homeId) throws NetatmoException {
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMESTATUS, PARAM_HOMEID, homeId);
|
||||
|
||||
NAHomeStatusResponse response = get(uriBuilder, NAHomeStatusResponse.class);
|
||||
NAHomeStatus body = response.getBody();
|
||||
return body != null ? body.getHomeStatus().orElse(null) : null;
|
||||
}
|
||||
|
||||
public @Nullable HomeData getHomeData(String homeId) throws NetatmoException {
|
||||
Collection<HomeData> result = getHomesData(homeId, null);
|
||||
return result.isEmpty() ? null : result.iterator().next();
|
||||
}
|
||||
|
||||
public Collection<HomeData> getHomesData(@Nullable String homeId, @Nullable ModuleType type)
|
||||
throws NetatmoException {
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMES_DATA, PARAM_HOMEID, homeId);
|
||||
|
||||
if (type != null) {
|
||||
uriBuilder.queryParam(PARAM_GATEWAYTYPE, type.name());
|
||||
}
|
||||
|
||||
HomeData.HomesDataResponse response = get(uriBuilder, HomeData.HomesDataResponse.class);
|
||||
ListBodyResponse<HomeData> body = response.getBody();
|
||||
return body != null ? body.getElements() : Set.of();
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link ListBodyResponse} models a response returned by API call containing
|
||||
* a list of elements.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ListBodyResponse<T extends NAObject> {
|
||||
@SerializedName(value = "devices", alternate = { "homes", "events_list", "events" })
|
||||
private NAObjectMap<T> elements = new NAObjectMap<>();
|
||||
|
||||
@Nullable
|
||||
T getElement(String id) {
|
||||
return elements.get(id);
|
||||
}
|
||||
|
||||
public Collection<T> getElements() {
|
||||
return elements.values();
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError;
|
||||
|
||||
/**
|
||||
* An exception that occurred while communicating with Netatmo server or related processes.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NetatmoException extends IOException {
|
||||
private static final long serialVersionUID = 1513549973502021727L;
|
||||
private ServiceError statusCode = ServiceError.UNKNOWN;
|
||||
|
||||
public NetatmoException(String format, Object... args) {
|
||||
super(String.format(format, args));
|
||||
}
|
||||
|
||||
public NetatmoException(Exception e, String format, Object... args) {
|
||||
super(String.format(format, args), e);
|
||||
}
|
||||
|
||||
public NetatmoException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NetatmoException(ApiError error) {
|
||||
super(error.getMessage());
|
||||
this.statusCode = error.getCode();
|
||||
}
|
||||
|
||||
public ServiceError getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getMessage() {
|
||||
String message = super.getMessage();
|
||||
return message == null ? null
|
||||
: String.format("Rest call failed: statusCode=%s, message=%s", statusCode, message);
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpMethod.POST;
|
||||
import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
|
||||
/**
|
||||
* Base class for all various rest managers
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class RestManager {
|
||||
private static final UriBuilder API_BASE_BUILDER = UriBuilder.fromUri(URL_API);
|
||||
private static final UriBuilder APP_URI_BUILDER = UriBuilder.fromUri(URL_APP).path(PATH_API);
|
||||
private static final UriBuilder API_URI_BUILDER = getApiBaseBuilder().path(PATH_API);
|
||||
|
||||
private final Set<Scope> requiredScopes;
|
||||
private final ApiBridgeHandler apiBridge;
|
||||
|
||||
public RestManager(ApiBridgeHandler apiBridge, FeatureArea features) {
|
||||
this.requiredScopes = features.scopes;
|
||||
this.apiBridge = apiBridge;
|
||||
}
|
||||
|
||||
protected <T extends ApiResponse<?>> T get(UriBuilder uriBuilder, Class<T> clazz) throws NetatmoException {
|
||||
return executeUri(uriBuilder, HttpMethod.GET, clazz, null, null);
|
||||
}
|
||||
|
||||
protected <T extends ApiResponse<?>> T post(UriBuilder uriBuilder, Class<T> clazz, @Nullable String payload,
|
||||
@Nullable String contentType) throws NetatmoException {
|
||||
return executeUri(uriBuilder, HttpMethod.POST, clazz, payload, contentType);
|
||||
}
|
||||
|
||||
protected <T> T post(URI uri, Class<T> clazz, Map<String, String> entries) throws NetatmoException {
|
||||
return apiBridge.executeUri(uri, POST, clazz, toRequest(entries),
|
||||
"application/x-www-form-urlencoded;charset=UTF-8", 3);
|
||||
}
|
||||
|
||||
private <T extends ApiResponse<?>> T executeUri(UriBuilder uriBuilder, HttpMethod method, Class<T> clazz,
|
||||
@Nullable String payload, @Nullable String contentType) throws NetatmoException {
|
||||
URI uri = uriBuilder.build();
|
||||
T response = apiBridge.executeUri(uri, method, clazz, payload, contentType, 3);
|
||||
if (response instanceof ApiResponse.Ok && ((ApiResponse.Ok) response).failed()) {
|
||||
throw new NetatmoException("Command failed : %s for uri : %s", response.getStatus(), uri.toString());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private static UriBuilder appendParams(UriBuilder builder, @Nullable Object... params) {
|
||||
if (params.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("appendParams : params count must be even");
|
||||
}
|
||||
for (int i = 0; i < params.length; i += 2) {
|
||||
Object query = params[i];
|
||||
if (query instanceof String) {
|
||||
Object param = params[i + 1];
|
||||
if (param != null) {
|
||||
builder.queryParam((String) query, param);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("appendParams : even parameters must be Strings");
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
protected static UriBuilder getApiBaseBuilder() {
|
||||
return API_BASE_BUILDER.clone();
|
||||
}
|
||||
|
||||
public static UriBuilder getApiUriBuilder(String path, @Nullable Object... params) {
|
||||
return appendParams(API_URI_BUILDER.clone().path(path), params);
|
||||
}
|
||||
|
||||
protected static UriBuilder getAppUriBuilder(String path, @Nullable Object... params) {
|
||||
return appendParams(APP_URI_BUILDER.clone().path(path), params);
|
||||
}
|
||||
|
||||
private String toRequest(Map<String, String> entries) {
|
||||
return entries.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
|
||||
}
|
||||
|
||||
public Set<Scope> getRequiredScopes() {
|
||||
return requiredScopes;
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.Home;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent.NAEventsDataResponse;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.Ping;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
|
||||
/**
|
||||
* Base class for all Security related endpoints
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SecurityApi extends RestManager {
|
||||
public SecurityApi(ApiBridgeHandler apiClient) {
|
||||
super(apiClient, FeatureArea.SECURITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dissociates a webhook from a user.
|
||||
*
|
||||
* @throws NetatmoException If fail to call the API, e.g. server error or deserializing
|
||||
*/
|
||||
public void dropWebhook() throws NetatmoException {
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_DROPWEBHOOK);
|
||||
post(uriBuilder, ApiResponse.Ok.class, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a callback url to a user.
|
||||
*
|
||||
* @param uri Your webhook callback url (required)
|
||||
* @throws NetatmoException If fail to call the API, e.g. server error or deserializing
|
||||
*/
|
||||
public void addwebhook(URI uri) throws NetatmoException {
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_ADDWEBHOOK, PARAM_URL, uri.toString());
|
||||
post(uriBuilder, ApiResponse.Ok.class, null, null);
|
||||
}
|
||||
|
||||
public Collection<HomeEvent> getPersonEvents(String homeId, String personId) throws NetatmoException {
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETEVENTS, PARAM_HOMEID, homeId, PARAM_PERSONID, personId,
|
||||
PARAM_OFFSET, 1);
|
||||
NAEventsDataResponse response = get(uriBuilder, NAEventsDataResponse.class);
|
||||
BodyResponse<Home> body = response.getBody();
|
||||
if (body != null) {
|
||||
Home home = body.getElement();
|
||||
if (home != null) {
|
||||
return home.getEvents().stream().filter(event -> personId.equals(event.getPersonId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
throw new NetatmoException("home should not be null");
|
||||
}
|
||||
|
||||
public Collection<HomeEvent> getCameraEvents(String homeId, String cameraId) throws NetatmoException {
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETEVENTS, PARAM_HOMEID, homeId, PARAM_DEVICEID, cameraId);
|
||||
NAEventsDataResponse response = get(uriBuilder, NAEventsDataResponse.class);
|
||||
BodyResponse<Home> body = response.getBody();
|
||||
if (body != null) {
|
||||
Home home = body.getElement();
|
||||
if (home != null) {
|
||||
return home.getEvents();
|
||||
}
|
||||
}
|
||||
throw new NetatmoException("home should not be null");
|
||||
}
|
||||
|
||||
public String ping(String vpnUrl) throws NetatmoException {
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(vpnUrl).path(PATH_COMMAND).path(SUB_PATH_PING);
|
||||
Ping response = get(uriBuilder, Ping.class);
|
||||
return response.getStatus();
|
||||
}
|
||||
|
||||
public void changeStatus(String localCameraURL, boolean setOn) throws NetatmoException {
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(localCameraURL).path(PATH_COMMAND).path(SUB_PATH_CHANGESTATUS);
|
||||
uriBuilder.queryParam(PARAM_STATUS, setOn ? "on" : "off");
|
||||
post(uriBuilder, ApiResponse.Ok.class, null, null);
|
||||
}
|
||||
|
||||
public void changeFloodLightMode(String localCameraURL, FloodLightMode mode) throws NetatmoException {
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(localCameraURL).path(PATH_COMMAND).path(SUB_PATH_FLOODLIGHTSET);
|
||||
uriBuilder.queryParam("config", "%7B%22mode%22:%22" + mode.toString() + "%22%7D");
|
||||
get(uriBuilder, ApiResponse.Ok.class);
|
||||
}
|
||||
|
||||
public void setPersonAwayStatus(String homeId, String personId, boolean away) throws NetatmoException {
|
||||
UriBuilder uriBuilder = getAppUriBuilder(away ? SUB_PATH_PERSON_AWAY : SUB_PATH_PERSON_HOME);
|
||||
String payload = String.format(
|
||||
away ? "{\"home_id\":\"%s\",\"person_id\":\"%s\"}" : "{\"home_id\":\"%s\",\"person_ids\":[\"%s\"]}",
|
||||
homeId, personId);
|
||||
post(uriBuilder, ApiResponse.Ok.class, payload, "application/json;charset=utf-8");
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.MeasureBodyElem;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAMain;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAMain.StationDataResponse;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
|
||||
/**
|
||||
* Base class for all Weather related endpoints
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WeatherApi extends RestManager {
|
||||
private class NAMeasuresResponse extends ApiResponse<List<MeasureBodyElem<Double>>> {
|
||||
}
|
||||
|
||||
private class NADateMeasuresResponse extends ApiResponse<List<MeasureBodyElem<ZonedDateTime>>> {
|
||||
}
|
||||
|
||||
public WeatherApi(ApiBridgeHandler apiClient) {
|
||||
super(apiClient, FeatureArea.WEATHER);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns data from a user's Weather Stations (measures and device specific data);
|
||||
*
|
||||
* @param deviceId Id of the device you want to retrieve information of (optional)
|
||||
* @param getFavorites Whether to include the user's favorite Weather Stations in addition to the user's
|
||||
* own Weather Stations (optional, default to false)
|
||||
* @return StationDataResponse
|
||||
* @throws NetatmoException If fail to call the API, e.g. server error or deserializing
|
||||
*/
|
||||
public StationDataResponse getStationsData(@Nullable String deviceId, boolean getFavorites)
|
||||
throws NetatmoException {
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETSTATION, PARAM_DEVICEID, deviceId, //
|
||||
PARAM_FAVORITES, getFavorites);
|
||||
StationDataResponse response = get(uriBuilder, StationDataResponse.class);
|
||||
return response;
|
||||
}
|
||||
|
||||
public NAMain getStationData(String deviceId) throws NetatmoException {
|
||||
ListBodyResponse<NAMain> answer = getStationsData(deviceId, true).getBody();
|
||||
if (answer != null) {
|
||||
NAMain station = answer.getElement(deviceId);
|
||||
if (station != null) {
|
||||
return station;
|
||||
}
|
||||
}
|
||||
throw new NetatmoException("Unexpected answer searching device '%s' : not found.", deviceId);
|
||||
}
|
||||
|
||||
public @Nullable Object getMeasures(String deviceId, @Nullable String moduleId, @Nullable String scale,
|
||||
String apiDescriptor) throws NetatmoException {
|
||||
MeasureBodyElem<?> result = getMeasure(deviceId, moduleId, scale, apiDescriptor);
|
||||
return result.getSingleValue();
|
||||
}
|
||||
|
||||
public @Nullable Object getMeasures(String deviceId, @Nullable String moduleId, @Nullable String scale,
|
||||
String apiDescriptor, String limit) throws NetatmoException {
|
||||
String queryLimit = limit;
|
||||
if (!apiDescriptor.contains("_")) {
|
||||
queryLimit += "_" + apiDescriptor;
|
||||
}
|
||||
|
||||
MeasureBodyElem<?> result = getMeasure(deviceId, moduleId, scale, queryLimit.toLowerCase());
|
||||
return result.getSingleValue();
|
||||
}
|
||||
|
||||
private MeasureBodyElem<?> getMeasure(String deviceId, @Nullable String moduleId, @Nullable String scale,
|
||||
String measureType) throws NetatmoException {
|
||||
// NAMeasuresResponse is not designed for optimize=false
|
||||
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETMEASURE, PARAM_DEVICEID, deviceId, "real_time", true,
|
||||
"date_end", "last", "optimize", true, "type", measureType.toLowerCase(), PARAM_MODULEID, moduleId);
|
||||
|
||||
if (scale != null) {
|
||||
uriBuilder.queryParam("scale", scale.toLowerCase());
|
||||
}
|
||||
if (measureType.startsWith("date")) {
|
||||
NADateMeasuresResponse response = get(uriBuilder, NADateMeasuresResponse.class);
|
||||
List<MeasureBodyElem<ZonedDateTime>> body = response.getBody();
|
||||
if (body != null && !body.isEmpty()) {
|
||||
return body.get(0);
|
||||
}
|
||||
} else {
|
||||
NAMeasuresResponse response = get(uriBuilder, NAMeasuresResponse.class);
|
||||
List<MeasureBodyElem<Double>> body = response.getBody();
|
||||
if (body != null && !body.isEmpty()) {
|
||||
return body.get(0);
|
||||
}
|
||||
}
|
||||
throw new NetatmoException("Empty response while getting measurements");
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This enum describes sub events in relation to a given event
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum EventSubType {
|
||||
SD_CARD_MISSING(List.of(EventType.SD), 1),
|
||||
SD_CARD_INSERTED(List.of(EventType.SD), 2),
|
||||
SD_CARD_FORMATTED(List.of(EventType.SD), 3),
|
||||
SD_CARD_WORKING(List.of(EventType.SD), 4),
|
||||
SD_CARD_DEFECTIVE(List.of(EventType.SD), 5),
|
||||
SD_CARD_INCOMPATIBLE_SPEED(List.of(EventType.SD), 6),
|
||||
SD_CARD_INSUFFICIENT_SPACE(List.of(EventType.SD), 7),
|
||||
ALIM_INCORRECT_POWER(List.of(EventType.ALIM), 1),
|
||||
ALIM_CORRECT_POWER(List.of(EventType.ALIM), 2),
|
||||
|
||||
// Artificially implemented by the binding subtypes
|
||||
PERSON_ARRIVAL(List.of(EventType.PERSON, EventType.PERSON_HOME), 1),
|
||||
PERSON_SEEN(List.of(EventType.PERSON), 2),
|
||||
PERSON_DEPARTURE(List.of(EventType.PERSON_AWAY), 1),
|
||||
MOVEMENT_HUMAN(List.of(EventType.MOVEMENT, EventType.HUMAN), 1),
|
||||
MOVEMENT_VEHICLE(List.of(EventType.MOVEMENT), 2),
|
||||
MOVEMENT_ANIMAL(List.of(EventType.MOVEMENT, EventType.ANIMAL), 3);
|
||||
|
||||
public final List<EventType> types;
|
||||
public final int subType;
|
||||
|
||||
EventSubType(List<EventType> types, int i) {
|
||||
this.types = types;
|
||||
this.subType = i;
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.data;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This enum describes events generated by webhooks and the type of
|
||||
* module they are related to according to API documentation
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum EventType {
|
||||
UNKNOWN(),
|
||||
|
||||
@SerializedName("person") // When the Indoor Camera detects a face
|
||||
PERSON(ModuleType.PERSON, ModuleType.WELCOME),
|
||||
|
||||
@SerializedName("person_away") // When geofencing indicates that the person has left the home
|
||||
PERSON_AWAY(ModuleType.PERSON, ModuleType.HOME),
|
||||
|
||||
@SerializedName("person_home") // When the person is declared at home
|
||||
PERSON_HOME(ModuleType.PERSON, ModuleType.HOME),
|
||||
|
||||
@SerializedName("outdoor") // When the Outdoor Camera detects a human, a car or an animal
|
||||
OUTDOOR(ModuleType.PRESENCE, ModuleType.DOORBELL),
|
||||
|
||||
@SerializedName("daily_summary") // When the Outdoor Camera video summary of the last 24 hours is available
|
||||
DAILY_SUMMARY(ModuleType.PRESENCE),
|
||||
|
||||
@SerializedName("movement") // When the Indoor Camera detects motion
|
||||
MOVEMENT(ModuleType.WELCOME),
|
||||
|
||||
@SerializedName("human") // When the Indoor Camera detects human motion
|
||||
HUMAN(ModuleType.WELCOME),
|
||||
|
||||
@SerializedName("animal") // When the Indoor Camera detects animal motion
|
||||
ANIMAL(ModuleType.WELCOME),
|
||||
|
||||
@SerializedName("new_module") // A new Module has been paired with the Indoor Camera
|
||||
NEW_MODULE(ModuleType.WELCOME),
|
||||
|
||||
@SerializedName("module_connect") // Module is connected with the Indoor Camera
|
||||
MODULE_CONNECT(ModuleType.WELCOME),
|
||||
|
||||
@SerializedName("module_disconnect") // Module lost its connection with the Indoor Camera
|
||||
MODULE_DISCONNECT(ModuleType.WELCOME),
|
||||
|
||||
@SerializedName("module_low_battery") // Module's battery is low
|
||||
MODULE_LOW_BATTERY(ModuleType.WELCOME),
|
||||
|
||||
@SerializedName("module_end_update") // Module's firmware update is over
|
||||
MODULE_END_UPDATE(ModuleType.WELCOME),
|
||||
|
||||
@SerializedName("connection") // When the Camera connects to Netatmo servers
|
||||
CONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE),
|
||||
|
||||
@SerializedName("disconnection") // When the Camera loses connection with Netatmo servers
|
||||
DISCONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE),
|
||||
|
||||
@SerializedName("on") // When Camera Monitoring is resumed
|
||||
ON(ModuleType.WELCOME, ModuleType.PRESENCE),
|
||||
|
||||
@SerializedName("off") // When Camera Monitoring is turned off
|
||||
OFF(ModuleType.WELCOME, ModuleType.PRESENCE),
|
||||
|
||||
@SerializedName("boot") // When the Camera is booting
|
||||
BOOT(ModuleType.WELCOME, ModuleType.PRESENCE),
|
||||
|
||||
@SerializedName("sd") // When Camera SD Card status changes
|
||||
SD(ModuleType.WELCOME, ModuleType.PRESENCE),
|
||||
|
||||
@SerializedName("alim") // When Camera power supply status changes
|
||||
ALIM(ModuleType.WELCOME, ModuleType.PRESENCE);
|
||||
|
||||
private final Set<ModuleType> appliesTo;
|
||||
|
||||
EventType(ModuleType... appliesTo) {
|
||||
this.appliesTo = Set.of(appliesTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
|
||||
public boolean appliesOn(ModuleType searched) {
|
||||
return appliesTo.contains(searched);
|
||||
}
|
||||
}
|
@ -0,0 +1,225 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.data;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID;
|
||||
import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.AirCareCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.CameraCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.Capability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.RoomCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityExtChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.BatteryChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.BatteryExtChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.EventChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.EventPersonChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.HomeEnergyChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.HomeSecurityChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.HumidityChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.LocationChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.MeasuresChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.NoiseChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.PersonChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.PresenceChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.PressureChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.PressureExtChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.RainChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.RoomChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.SetpointChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.SignalChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureExtChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureOutChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.Therm1ChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampExtChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.WindChannelHelper;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This enum all handled Netatmo modules and devices along with their capabilities
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ModuleType {
|
||||
UNKNOWN(FeatureArea.NONE, null, null, List.of(), List.of()),
|
||||
ACCOUNT(FeatureArea.NONE, null, null, List.of(), List.of()),
|
||||
@SerializedName("NAHome")
|
||||
HOME(FeatureArea.NONE, "NAHome", ACCOUNT,
|
||||
List.of(DeviceCapability.class, EventCapability.class, HomeCapability.class, ChannelHelperCapability.class),
|
||||
List.of(HomeSecurityChannelHelper.class, HomeEnergyChannelHelper.class)),
|
||||
@SerializedName("NAPerson")
|
||||
PERSON(FeatureArea.SECURITY, "NAPerson", HOME,
|
||||
List.of(EventCapability.class, PersonCapability.class, ChannelHelperCapability.class),
|
||||
List.of(PersonChannelHelper.class, EventPersonChannelHelper.class)),
|
||||
@SerializedName("NACamera")
|
||||
WELCOME(FeatureArea.SECURITY, "NACamera", HOME,
|
||||
List.of(EventCapability.class, CameraCapability.class, ChannelHelperCapability.class),
|
||||
List.of(CameraChannelHelper.class, SignalChannelHelper.class, EventChannelHelper.class)),
|
||||
@SerializedName("NOC")
|
||||
PRESENCE(FeatureArea.SECURITY, "NOC", HOME,
|
||||
List.of(EventCapability.class, PresenceCapability.class, ChannelHelperCapability.class),
|
||||
List.of(CameraChannelHelper.class, PresenceChannelHelper.class, SignalChannelHelper.class,
|
||||
EventChannelHelper.class)),
|
||||
@SerializedName("NIS")
|
||||
SIREN(FeatureArea.SECURITY, "NIS", HOME, List.of(ChannelHelperCapability.class),
|
||||
List.of(BatteryChannelHelper.class, TimestampChannelHelper.class, SignalChannelHelper.class)),
|
||||
@SerializedName("NDB")
|
||||
DOORBELL(FeatureArea.SECURITY, "NDB", HOME, List.of(ChannelHelperCapability.class),
|
||||
List.of(SignalChannelHelper.class)),
|
||||
@SerializedName("NAMain")
|
||||
WEATHER_STATION(FeatureArea.WEATHER, "NAMain", ACCOUNT,
|
||||
List.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class,
|
||||
ChannelHelperCapability.class),
|
||||
List.of(PressureExtChannelHelper.class, NoiseChannelHelper.class, HumidityChannelHelper.class,
|
||||
TemperatureExtChannelHelper.class, AirQualityChannelHelper.class, LocationChannelHelper.class,
|
||||
TimestampExtChannelHelper.class, MeasuresChannelHelper.class, SignalChannelHelper.class)),
|
||||
@SerializedName("NAModule1")
|
||||
OUTDOOR(FeatureArea.WEATHER, "NAModule1", WEATHER_STATION,
|
||||
List.of(MeasureCapability.class, ChannelHelperCapability.class),
|
||||
List.of(HumidityChannelHelper.class, TemperatureOutChannelHelper.class, BatteryChannelHelper.class,
|
||||
MeasuresChannelHelper.class, TimestampExtChannelHelper.class, SignalChannelHelper.class)),
|
||||
@SerializedName("NAModule2")
|
||||
WIND(FeatureArea.WEATHER, "NAModule2", WEATHER_STATION, List.of(ChannelHelperCapability.class),
|
||||
List.of(WindChannelHelper.class, BatteryChannelHelper.class, TimestampExtChannelHelper.class,
|
||||
SignalChannelHelper.class)),
|
||||
@SerializedName("NAModule3")
|
||||
RAIN(FeatureArea.WEATHER, "NAModule3", WEATHER_STATION,
|
||||
List.of(MeasureCapability.class, ChannelHelperCapability.class),
|
||||
List.of(RainChannelHelper.class, BatteryChannelHelper.class, MeasuresChannelHelper.class,
|
||||
TimestampExtChannelHelper.class, SignalChannelHelper.class)),
|
||||
@SerializedName("NAModule4")
|
||||
INDOOR(FeatureArea.WEATHER, "NAModule4", WEATHER_STATION,
|
||||
List.of(MeasureCapability.class, ChannelHelperCapability.class),
|
||||
List.of(HumidityChannelHelper.class, TemperatureExtChannelHelper.class, AirQualityChannelHelper.class,
|
||||
BatteryChannelHelper.class, MeasuresChannelHelper.class, TimestampExtChannelHelper.class,
|
||||
SignalChannelHelper.class)),
|
||||
@SerializedName("NHC")
|
||||
HOME_COACH(FeatureArea.AIR_CARE, "NHC", ACCOUNT,
|
||||
List.of(DeviceCapability.class, AirCareCapability.class, MeasureCapability.class,
|
||||
ChannelHelperCapability.class),
|
||||
List.of(NoiseChannelHelper.class, HumidityChannelHelper.class, AirQualityExtChannelHelper.class,
|
||||
TemperatureChannelHelper.class, PressureChannelHelper.class, TimestampExtChannelHelper.class,
|
||||
SignalChannelHelper.class, MeasuresChannelHelper.class, LocationChannelHelper.class)),
|
||||
@SerializedName("NAPlug")
|
||||
PLUG(FeatureArea.ENERGY, "NAPlug", HOME, List.of(ChannelHelperCapability.class),
|
||||
List.of(SignalChannelHelper.class)),
|
||||
@SerializedName("NATherm1")
|
||||
THERMOSTAT(FeatureArea.ENERGY, "NATherm1", HOME, List.of(ChannelHelperCapability.class),
|
||||
List.of(Therm1ChannelHelper.class, BatteryExtChannelHelper.class, SignalChannelHelper.class)),
|
||||
@SerializedName("NARoom")
|
||||
ROOM(FeatureArea.ENERGY, "NARoom", HOME, List.of(RoomCapability.class, ChannelHelperCapability.class),
|
||||
List.of(RoomChannelHelper.class, SetpointChannelHelper.class)),
|
||||
@SerializedName("NRV")
|
||||
VALVE(FeatureArea.ENERGY, "NRV", HOME, List.of(ChannelHelperCapability.class),
|
||||
List.of(BatteryExtChannelHelper.class, SignalChannelHelper.class));
|
||||
|
||||
public static final EnumSet<ModuleType> AS_SET = EnumSet.allOf(ModuleType.class);
|
||||
|
||||
private final @Nullable ModuleType bridgeType;
|
||||
public final List<String> groupTypes = new LinkedList<>();
|
||||
public final List<String> extensions = new LinkedList<>();
|
||||
public final List<Class<? extends ChannelHelper>> channelHelpers;
|
||||
public final List<Class<? extends Capability>> capabilities;
|
||||
public final ThingTypeUID thingTypeUID;
|
||||
public final FeatureArea feature;
|
||||
public final @Nullable String apiName;
|
||||
|
||||
ModuleType(FeatureArea feature, @Nullable String apiName, @Nullable ModuleType bridge,
|
||||
List<Class<? extends Capability>> capabilities, List<Class<? extends ChannelHelper>> helpers) {
|
||||
this.channelHelpers = helpers;
|
||||
this.bridgeType = bridge;
|
||||
this.feature = feature;
|
||||
this.capabilities = capabilities;
|
||||
this.apiName = apiName;
|
||||
thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase().replace("_", "-"));
|
||||
try {
|
||||
for (Class<? extends ChannelHelper> helperClass : helpers) {
|
||||
ChannelHelper helper = helperClass.getConstructor().newInstance();
|
||||
groupTypes.addAll(helper.getChannelGroupTypes());
|
||||
extensions.addAll(helper.getExtensibleChannels());
|
||||
}
|
||||
} catch (RuntimeException | ReflectiveOperationException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLogical() {
|
||||
return !channelHelpers.contains(SignalChannelHelper.class);
|
||||
}
|
||||
|
||||
public boolean isABridge() {
|
||||
for (ModuleType mt : ModuleType.values()) {
|
||||
if (this.equals(mt.bridgeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int[] getSignalLevels() {
|
||||
if (!isLogical()) {
|
||||
return (channelHelpers.contains(BatteryChannelHelper.class)
|
||||
|| channelHelpers.contains(BatteryExtChannelHelper.class)) ? RADIO_SIGNAL_LEVELS
|
||||
: WIFI_SIGNAL_LEVELS;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"This should not be called for module type : " + name() + ", please file a bug report.");
|
||||
}
|
||||
|
||||
public ModuleType getBridge() {
|
||||
ModuleType bridge = bridgeType;
|
||||
return bridge != null ? bridge : ModuleType.UNKNOWN;
|
||||
}
|
||||
|
||||
public URI getConfigDescription() {
|
||||
return URI.create(BINDING_ID + ":"
|
||||
+ (equals(ACCOUNT) ? "api_bridge"
|
||||
: equals(HOME) ? "home"
|
||||
: (isLogical() ? "virtual"
|
||||
: ModuleType.UNKNOWN.equals(getBridge()) ? "configurable" : "device")));
|
||||
}
|
||||
|
||||
public static ModuleType from(ThingTypeUID thingTypeUID) {
|
||||
return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException());
|
||||
}
|
||||
|
||||
public static ModuleType from(String apiName) {
|
||||
return ModuleType.AS_SET.stream().filter(mt -> apiName.equals(mt.apiName)).findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException());
|
||||
}
|
||||
}
|
@ -0,0 +1,401 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.data;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
import static org.openhab.core.library.CoreItemFactory.*;
|
||||
import static org.openhab.core.library.unit.MetricPrefix.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.types.StateDescriptionFragment;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.openhab.core.types.util.UnitUtils;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This class holds various definitions and settings provided by the Netatmo
|
||||
* API documentation
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NetatmoConstants {
|
||||
public static class Measure {
|
||||
public final double minValue;
|
||||
public final double maxValue;
|
||||
public final int scale;
|
||||
public final Unit<?> unit;
|
||||
|
||||
private Measure(double minValue, double maxValue, double precision, Unit<?> unit) {
|
||||
this.minValue = minValue;
|
||||
this.maxValue = maxValue;
|
||||
this.unit = unit;
|
||||
String[] splitter = Double.valueOf(precision).toString().split("\\.");
|
||||
if (splitter.length > 1) {
|
||||
int dec = Integer.parseInt(splitter[1]);
|
||||
this.scale = dec > 0 ? Integer.toString(dec).length() : 0;
|
||||
} else {
|
||||
this.scale = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class MeasureChannelDetails {
|
||||
private static final StateDescriptionFragmentBuilder BUILDER = StateDescriptionFragmentBuilder.create();
|
||||
public final URI configURI;
|
||||
public final String itemType;
|
||||
public final StateDescriptionFragment stateDescriptionFragment;
|
||||
|
||||
private MeasureChannelDetails(String measureType, String itemType, String pattern) {
|
||||
this.configURI = URI.create(String.join(":", BINDING_ID, measureType, "config"));
|
||||
this.itemType = itemType;
|
||||
this.stateDescriptionFragment = BUILDER.withReadOnly(true).withPattern(pattern).build();
|
||||
}
|
||||
}
|
||||
|
||||
public enum MeasureClass {
|
||||
INSIDE_TEMPERATURE(0, 50, 0.3, SIUnits.CELSIUS, "temp", "measure", true),
|
||||
OUTSIDE_TEMPERATURE(-40, 65, 0.3, SIUnits.CELSIUS, "temp", "measure", true),
|
||||
HEAT_INDEX(-40, 65, 1, SIUnits.CELSIUS, "", "", false),
|
||||
PRESSURE(260, 1260, 1, HECTO(SIUnits.PASCAL), "pressure", "measure", true),
|
||||
CO2(0, 5000, 50, Units.PARTS_PER_MILLION, "co2", "measure", true),
|
||||
NOISE(35, 120, 1, Units.DECIBEL, "noise", "measure", true),
|
||||
RAIN_QUANTITY(0, 150, 0.1, MILLI(SIUnits.METRE), "sum_rain", "sum_rain", false),
|
||||
RAIN_INTENSITY(0, 150, 0.1, Units.MILLIMETRE_PER_HOUR, "", "", false),
|
||||
WIND_SPEED(0, 160, 1.8, SIUnits.KILOMETRE_PER_HOUR, "", "", false),
|
||||
WIND_ANGLE(0, 360, 5, Units.DEGREE_ANGLE, "", "", false),
|
||||
HUMIDITY(0, 100, 3, Units.PERCENT, "hum", "measure", true);
|
||||
|
||||
public static final EnumSet<MeasureClass> AS_SET = EnumSet.allOf(MeasureClass.class);
|
||||
|
||||
public final Measure measureDefinition;
|
||||
public final String apiDescriptor;
|
||||
public final Map<String, MeasureChannelDetails> channels = new HashMap<>(2);
|
||||
|
||||
MeasureClass(double min, double max, double precision, Unit<?> unit, String apiDescriptor, String confFragment,
|
||||
boolean canScale) {
|
||||
this.measureDefinition = new Measure(min, max, precision, unit);
|
||||
this.apiDescriptor = apiDescriptor;
|
||||
if (!apiDescriptor.isBlank()) {
|
||||
String dimension = UnitUtils.getDimensionName(unit);
|
||||
|
||||
channels.put(String.join("-", apiDescriptor, "measurement"),
|
||||
new MeasureChannelDetails(confFragment, String.join(":", NUMBER, dimension),
|
||||
String.format("%%.%df %s", measureDefinition.scale, UnitUtils.UNIT_PLACEHOLDER)));
|
||||
if (canScale) {
|
||||
channels.put(String.join("-", apiDescriptor, GROUP_TIMESTAMP),
|
||||
new MeasureChannelDetails(GROUP_TIMESTAMP, DATETIME, "%1$tA, %1$td.%1$tm. %1$tH:%1$tM"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Netatmo API urls
|
||||
public static final String URL_API = "https://api.netatmo.com/";
|
||||
public static final String URL_APP = "https://app.netatmo.net/";
|
||||
public static final String PATH_OAUTH = "oauth2/token";
|
||||
public static final String PATH_API = "api";
|
||||
public static final String PATH_COMMAND = "command";
|
||||
public static final String SUB_PATH_PERSON_AWAY = "setpersonsaway";
|
||||
public static final String SUB_PATH_PERSON_HOME = "setpersonshome";
|
||||
public static final String SUB_PATH_HOMES_DATA = "homesdata";
|
||||
public static final String SUB_PATH_ADDWEBHOOK = "addwebhook";
|
||||
public static final String SUB_PATH_DROPWEBHOOK = "dropwebhook";
|
||||
public static final String SUB_PATH_SETROOMTHERMPOINT = "setroomthermpoint";
|
||||
public static final String SUB_PATH_SETTHERMMODE = "setthermmode";
|
||||
public static final String SUB_PATH_SWITCHSCHEDULE = "switchschedule";
|
||||
public static final String SUB_PATH_GETSTATION = "getstationsdata";
|
||||
public static final String SUB_PATH_GETMEASURE = "getmeasure";
|
||||
public static final String SUB_PATH_HOMESTATUS = "homestatus";
|
||||
public static final String SUB_PATH_HOMECOACH = "gethomecoachsdata";
|
||||
public static final String SUB_PATH_GETEVENTS = "getevents";
|
||||
public static final String SUB_PATH_PING = "ping";
|
||||
public static final String SUB_PATH_CHANGESTATUS = "changestatus";
|
||||
public static final String SUB_PATH_FLOODLIGHTSET = "floodlight_set_config";
|
||||
public static final String PARAM_DEVICEID = "device_id";
|
||||
public static final String PARAM_MODULEID = "module_id";
|
||||
public static final String PARAM_HOMEID = "home_id";
|
||||
public static final String PARAM_ROOMID = "room_id";
|
||||
public static final String PARAM_PERSONID = "person_id";
|
||||
public static final String PARAM_SCHEDULEID = "schedule_id";
|
||||
public static final String PARAM_OFFSET = "offset";
|
||||
public static final String PARAM_GATEWAYTYPE = "gateway_types";
|
||||
public static final String PARAM_MODE = "mode";
|
||||
public static final String PARAM_URL = "url";
|
||||
public static final String PARAM_FAVORITES = "get_favorites";
|
||||
public static final String PARAM_STATUS = "status";
|
||||
|
||||
// Global variables
|
||||
public static final int THERM_MAX_SETPOINT = 30;
|
||||
|
||||
// Token scopes
|
||||
public static enum Scope {
|
||||
@SerializedName("read_station")
|
||||
READ_STATION,
|
||||
@SerializedName("read_thermostat")
|
||||
READ_THERMOSTAT,
|
||||
@SerializedName("write_thermostat")
|
||||
WRITE_THERMOSTAT,
|
||||
@SerializedName("read_camera")
|
||||
READ_CAMERA,
|
||||
@SerializedName("write_camera")
|
||||
WRITE_CAMERA,
|
||||
@SerializedName("access_camera")
|
||||
ACCESS_CAMERA,
|
||||
@SerializedName("read_presence")
|
||||
READ_PRESENCE,
|
||||
@SerializedName("write_presence")
|
||||
WRITE_PRESENCE,
|
||||
@SerializedName("access_presence")
|
||||
ACCESS_PRESENCE,
|
||||
@SerializedName("read_smokedetector")
|
||||
READ_SMOKEDETECTOR,
|
||||
@SerializedName("read_homecoach")
|
||||
READ_HOMECOACH,
|
||||
@SerializedName("read_doorbell")
|
||||
READ_DOORBELL,
|
||||
@SerializedName("write_doorbell")
|
||||
WRITE_DOORBELL,
|
||||
@SerializedName("access_doorbell")
|
||||
ACCESS_DOORBELL,
|
||||
UNKNOWN;
|
||||
}
|
||||
|
||||
private static final Set<Scope> SMOKE = Set.of(Scope.READ_SMOKEDETECTOR);
|
||||
private static final Set<Scope> WELCOME = Set.of(Scope.READ_CAMERA, Scope.WRITE_CAMERA, Scope.ACCESS_CAMERA);
|
||||
private static final Set<Scope> DOORBELL = Set.of(Scope.READ_DOORBELL, Scope.WRITE_DOORBELL, Scope.ACCESS_DOORBELL);
|
||||
private static final Set<Scope> PRESENCE = Set.of(Scope.READ_PRESENCE, Scope.WRITE_PRESENCE, Scope.ACCESS_PRESENCE);
|
||||
|
||||
// Radio signal quality thresholds
|
||||
static final int[] WIFI_SIGNAL_LEVELS = new int[] { 99, 84, 69, 54 }; // Resp : bad, average, good, full
|
||||
static final int[] RADIO_SIGNAL_LEVELS = new int[] { 90, 80, 70, 60 }; // Resp : low, medium, high, full
|
||||
|
||||
public static enum FeatureArea {
|
||||
AIR_CARE(Scope.READ_HOMECOACH),
|
||||
WEATHER(Scope.READ_STATION),
|
||||
ENERGY(Scope.READ_THERMOSTAT, Scope.WRITE_THERMOSTAT),
|
||||
SECURITY(Stream.of(WELCOME, PRESENCE, SMOKE, DOORBELL).flatMap(Set::stream).toArray(Scope[]::new)),
|
||||
NONE();
|
||||
|
||||
public static final Set<FeatureArea> AS_SET = EnumSet.allOf(FeatureArea.class);
|
||||
|
||||
public static String toScopeString(Set<FeatureArea> featureSet) {
|
||||
return featureSet.stream().map(fa -> fa.scopes).flatMap(Set::stream).map(s -> s.name().toLowerCase())
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
public final Set<Scope> scopes;
|
||||
|
||||
FeatureArea(Scope... scopes) {
|
||||
this.scopes = Set.of(scopes);
|
||||
}
|
||||
}
|
||||
|
||||
// Thermostat definitions
|
||||
public static enum SetpointMode {
|
||||
@SerializedName("program")
|
||||
PROGRAM("program"),
|
||||
@SerializedName("away")
|
||||
AWAY("away"),
|
||||
@SerializedName("hg")
|
||||
FROST_GUARD("hg"),
|
||||
@SerializedName("manual")
|
||||
MANUAL("manual"),
|
||||
@SerializedName("off")
|
||||
OFF("off"),
|
||||
@SerializedName("max")
|
||||
MAX("max"),
|
||||
@SerializedName("schedule")
|
||||
SCHEDULE("schedule"),
|
||||
HOME("home"),
|
||||
UNKNOWN("");
|
||||
|
||||
public final String apiDescriptor;
|
||||
|
||||
SetpointMode(String descriptor) {
|
||||
this.apiDescriptor = descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum ThermostatZoneType {
|
||||
@SerializedName("0")
|
||||
DAY("0"),
|
||||
@SerializedName("1")
|
||||
NIGHT("1"),
|
||||
@SerializedName("2")
|
||||
AWAY("2"),
|
||||
@SerializedName("3")
|
||||
FROST_GUARD("3"),
|
||||
@SerializedName("4")
|
||||
CUSTOM("4"),
|
||||
@SerializedName("5")
|
||||
ECO("5"),
|
||||
@SerializedName("8")
|
||||
COMFORT("8"),
|
||||
UNKNOWN("");
|
||||
|
||||
public final String zoneId;
|
||||
|
||||
private ThermostatZoneType(String id) {
|
||||
zoneId = id;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FloodLightMode {
|
||||
@SerializedName("on")
|
||||
ON,
|
||||
@SerializedName("off")
|
||||
OFF,
|
||||
@SerializedName("auto")
|
||||
AUTO,
|
||||
UNKNOWN;
|
||||
}
|
||||
|
||||
public enum EventCategory {
|
||||
@SerializedName("human")
|
||||
HUMAN,
|
||||
@SerializedName("animal")
|
||||
ANIMAL,
|
||||
@SerializedName("vehicle")
|
||||
VEHICLE,
|
||||
UNKNOWN;
|
||||
}
|
||||
|
||||
public enum TrendDescription {
|
||||
@SerializedName("up")
|
||||
UP,
|
||||
@SerializedName("stable")
|
||||
STABLE,
|
||||
@SerializedName("down")
|
||||
DOWN,
|
||||
UNKNOWN;
|
||||
}
|
||||
|
||||
public enum VideoStatus {
|
||||
@SerializedName("recording")
|
||||
RECORDING,
|
||||
@SerializedName("available")
|
||||
AVAILABLE,
|
||||
@SerializedName("deleted")
|
||||
DELETED,
|
||||
UNKNOWN;
|
||||
}
|
||||
|
||||
public enum SdCardStatus {
|
||||
@SerializedName("1")
|
||||
SD_CARD_MISSING,
|
||||
@SerializedName("2")
|
||||
SD_CARD_INSERTED,
|
||||
@SerializedName("3")
|
||||
SD_CARD_FORMATTED,
|
||||
@SerializedName("4")
|
||||
SD_CARD_WORKING,
|
||||
@SerializedName("5")
|
||||
SD_CARD_DEFECTIVE,
|
||||
@SerializedName("6")
|
||||
SD_CARD_INCOMPATIBLE_SPEED,
|
||||
@SerializedName("7")
|
||||
SD_CARD_INSUFFICIENT_SPACE,
|
||||
UNKNOWN;
|
||||
}
|
||||
|
||||
public enum AlimentationStatus {
|
||||
@SerializedName("1")
|
||||
ALIM_INCORRECT_POWER,
|
||||
@SerializedName("2")
|
||||
ALIM_CORRECT_POWER,
|
||||
UNKNOWN;
|
||||
}
|
||||
|
||||
public enum BatteryState {
|
||||
@SerializedName("full")
|
||||
FULL(100),
|
||||
@SerializedName("high")
|
||||
HIGH(80),
|
||||
@SerializedName("medium")
|
||||
MEDIUM(50),
|
||||
@SerializedName("low")
|
||||
LOW(15),
|
||||
UNKNOWN(-1);
|
||||
|
||||
public final int level;
|
||||
|
||||
BatteryState(int i) {
|
||||
this.level = i;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ServiceError {
|
||||
@SerializedName("99")
|
||||
UNKNOWN,
|
||||
@SerializedName("-2")
|
||||
UNKNOWN_ERROR_IN_OAUTH,
|
||||
@SerializedName("-1")
|
||||
GRANT_IS_INVALID,
|
||||
@SerializedName("1")
|
||||
ACCESS_TOKEN_MISSING,
|
||||
@SerializedName("2")
|
||||
INVALID_TOKEN_MISSING,
|
||||
@SerializedName("3")
|
||||
ACCESS_TOKEN_EXPIRED,
|
||||
@SerializedName("5")
|
||||
APPLICATION_DEACTIVATED,
|
||||
@SerializedName("7")
|
||||
NOTHING_TO_MODIFY,
|
||||
@SerializedName("9")
|
||||
DEVICE_NOT_FOUND,
|
||||
@SerializedName("10")
|
||||
MISSING_ARGUMENTS,
|
||||
@SerializedName("13")
|
||||
OPERATION_FORBIDDEN,
|
||||
@SerializedName("19")
|
||||
IP_NOT_FOUND,
|
||||
@SerializedName("21")
|
||||
INVALID_ARGUMENT,
|
||||
@SerializedName("22")
|
||||
APPLICATION_NOT_FOUND,
|
||||
@SerializedName("23")
|
||||
USER_NOT_FOUND,
|
||||
@SerializedName("25")
|
||||
INVALID_DATE,
|
||||
@SerializedName("26")
|
||||
MAXIMUM_USAGE_REACHED,
|
||||
@SerializedName("30")
|
||||
INVALID_REFRESH_TOKEN,
|
||||
@SerializedName("31")
|
||||
METHOD_NOT_FOUND,
|
||||
@SerializedName("35")
|
||||
UNABLE_TO_EXECUTE,
|
||||
@SerializedName("36")
|
||||
PROHIBITED_STRING,
|
||||
@SerializedName("37")
|
||||
NO_MORE_SPACE_AVAILABLE_ON_THE_CAMERA,
|
||||
@SerializedName("40")
|
||||
JSON_GIVEN_HAS_AN_INVALID_ENCODING,
|
||||
@SerializedName("41")
|
||||
DEVICE_IS_UNREACHABLE;
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
|
||||
|
||||
/**
|
||||
* This is the Access Token Response, a simple value-object holding the result of an Access Token Request, as
|
||||
* provided by Netatmo API.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
public final class AccessTokenResponse {
|
||||
|
||||
/**
|
||||
* The access token issued by the authorization server. It is used
|
||||
* by the client to gain access to a resource.
|
||||
*
|
||||
*/
|
||||
private String accessToken;
|
||||
|
||||
/**
|
||||
* Number of seconds that this OAuthToken is valid for since the time it was created.
|
||||
*
|
||||
*/
|
||||
private long expiresIn;
|
||||
|
||||
/**
|
||||
* Refresh token is a string representing the authorization granted to
|
||||
* the client by the resource owner. Unlike access tokens, refresh tokens are
|
||||
* intended for use only with authorization servers and are never sent
|
||||
* to resource servers.
|
||||
*
|
||||
*/
|
||||
private String refreshToken;
|
||||
|
||||
private List<Scope> scope;
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public List<Scope> getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AccessTokenResponse [accessToken=" + accessToken + ", expiresIn=" + expiresIn + ", refreshToken="
|
||||
+ refreshToken + ", scope=" + scope + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.TrendDescription;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link Dashboard} holds data returned by API call supporting the dashboard functionality.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Dashboard {
|
||||
private @Nullable ZonedDateTime timeUtc;
|
||||
|
||||
@SerializedName("BoilerOn")
|
||||
private int boilerOn;
|
||||
|
||||
@SerializedName("BoilerOff")
|
||||
private int boilerOff;
|
||||
|
||||
@SerializedName("Temperature")
|
||||
private double temperature;
|
||||
|
||||
private TrendDescription pressureTrend = TrendDescription.UNKNOWN;
|
||||
private TrendDescription tempTrend = TrendDescription.UNKNOWN;
|
||||
private @Nullable ZonedDateTime dateMaxTemp;
|
||||
private @Nullable ZonedDateTime dateMinTemp;
|
||||
private double minTemp;
|
||||
private double maxTemp;
|
||||
@SerializedName("AbsolutePressure")
|
||||
private double absolutePressure;
|
||||
|
||||
@SerializedName("CO2")
|
||||
private double co2;
|
||||
|
||||
@SerializedName("Humidity")
|
||||
private double humidity;
|
||||
|
||||
@SerializedName("Noise")
|
||||
private double noise;
|
||||
|
||||
@SerializedName("Pressure")
|
||||
private double pressure;
|
||||
|
||||
@SerializedName("Rain")
|
||||
private double rain;
|
||||
@SerializedName("sum_rain_1")
|
||||
private double sumRain1;
|
||||
@SerializedName("sum_rain_24")
|
||||
private double sumRain24;
|
||||
|
||||
@SerializedName("WindAngle")
|
||||
private int windAngle;
|
||||
|
||||
@SerializedName("GustAngle")
|
||||
private int gustAngle;
|
||||
|
||||
@SerializedName("WindStrength")
|
||||
private int windStrength;
|
||||
|
||||
private int maxWindStr;
|
||||
private @Nullable ZonedDateTime dateMaxWindStr;
|
||||
|
||||
@SerializedName("GustStrength")
|
||||
private int gustStrength;
|
||||
|
||||
private int healthIdx;
|
||||
|
||||
public @Nullable ZonedDateTime getTimeUtc() {
|
||||
return timeUtc;
|
||||
}
|
||||
|
||||
public int getBoilerOn() {
|
||||
return boilerOn;
|
||||
}
|
||||
|
||||
public int getBoilerOff() {
|
||||
return boilerOff;
|
||||
}
|
||||
|
||||
public double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public TrendDescription getTempTrend() {
|
||||
return tempTrend;
|
||||
}
|
||||
|
||||
public @Nullable ZonedDateTime getDateMaxTemp() {
|
||||
return dateMaxTemp;
|
||||
}
|
||||
|
||||
public @Nullable ZonedDateTime getDateMinTemp() {
|
||||
return dateMinTemp;
|
||||
}
|
||||
|
||||
public double getMinTemp() {
|
||||
return minTemp;
|
||||
}
|
||||
|
||||
public double getMaxTemp() {
|
||||
return maxTemp;
|
||||
}
|
||||
|
||||
public double getAbsolutePressure() {
|
||||
return absolutePressure;
|
||||
}
|
||||
|
||||
public double getCo2() {
|
||||
return co2;
|
||||
}
|
||||
|
||||
public double getHumidity() {
|
||||
return humidity;
|
||||
}
|
||||
|
||||
public double getNoise() {
|
||||
return noise;
|
||||
}
|
||||
|
||||
public double getPressure() {
|
||||
return pressure;
|
||||
}
|
||||
|
||||
public TrendDescription getPressureTrend() {
|
||||
return pressureTrend;
|
||||
}
|
||||
|
||||
public double getRain() {
|
||||
return rain;
|
||||
}
|
||||
|
||||
public double getSumRain1() {
|
||||
return sumRain1;
|
||||
}
|
||||
|
||||
public double getSumRain24() {
|
||||
return sumRain24;
|
||||
}
|
||||
|
||||
public double getWindAngle() {
|
||||
return windAngle;
|
||||
}
|
||||
|
||||
public double getGustAngle() {
|
||||
return gustAngle;
|
||||
}
|
||||
|
||||
public double getWindStrength() {
|
||||
return windStrength;
|
||||
}
|
||||
|
||||
public double getMaxWindStr() {
|
||||
return maxWindStr;
|
||||
}
|
||||
|
||||
public @Nullable ZonedDateTime getDateMaxWindStr() {
|
||||
return dateMaxWindStr;
|
||||
}
|
||||
|
||||
public double getGustStrength() {
|
||||
return gustStrength;
|
||||
}
|
||||
|
||||
public int getHealthIdx() {
|
||||
return healthIdx;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
|
||||
/**
|
||||
* The {@link Device} holds common data for all Netatmo devices.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Device extends NAThing {
|
||||
private @Nullable NAObjectMap<Module> modules;
|
||||
private long dateSetup;
|
||||
private long lastUpgrade;
|
||||
private @Nullable Place place;
|
||||
|
||||
public NAObjectMap<Module> getModules() {
|
||||
NAObjectMap<Module> localModules = modules;
|
||||
return localModules != null ? localModules : new NAObjectMap<>();
|
||||
}
|
||||
|
||||
public long getDateSetup() {
|
||||
return dateSetup;
|
||||
}
|
||||
|
||||
public long getLastUpgrade() {
|
||||
return lastUpgrade;
|
||||
}
|
||||
|
||||
public Optional<Place> getPlace() {
|
||||
return Optional.ofNullable(place);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.EventSubType;
|
||||
import org.openhab.binding.netatmo.internal.api.data.EventType;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link Event} holds information transferred by the webhook.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public abstract class Event extends NAObject {
|
||||
protected EventType type = EventType.UNKNOWN;
|
||||
@SerializedName(value = "camera_id", alternate = { "module_id" })
|
||||
private String cameraId = "";
|
||||
protected int subType = -1;
|
||||
|
||||
public abstract ZonedDateTime getTime();
|
||||
|
||||
public abstract @Nullable String getSnapshotUrl();
|
||||
|
||||
public abstract @Nullable String getPersonId();
|
||||
|
||||
public EventType getEventType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getCameraId() {
|
||||
return cameraId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getName() {
|
||||
String localMessage = super.getName();
|
||||
return (localMessage != null ? localMessage.replace("<b>", "").replace("</b>", "") : "");
|
||||
}
|
||||
|
||||
public Optional<EventSubType> getSubTypeDescription() {
|
||||
return Stream.of(EventSubType.values()).filter(v -> v.types.contains(getEventType()) && v.subType == subType)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public void setEventType(EventType type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
|
||||
/**
|
||||
* The {@link Home} holds home information.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class Home extends Device implements Location {
|
||||
private double[] coordinates = {};
|
||||
private double altitude;
|
||||
private List<HomeEvent> events = List.of();
|
||||
|
||||
@Override
|
||||
public ModuleType getType() {
|
||||
return ModuleType.HOME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] getCoordinates() {
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
public List<HomeEvent> getEvents() {
|
||||
return events;
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.ApiResponse;
|
||||
import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
|
||||
/**
|
||||
* The {@link HomeData} holds home information returned by homesdata endpoint.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class HomeData extends NAThing implements NAModule, LocationEx {
|
||||
public class HomesDataResponse extends ApiResponse<ListBodyResponse<HomeData>> {
|
||||
}
|
||||
|
||||
private double altitude;
|
||||
private double[] coordinates = {};
|
||||
private @Nullable String country;
|
||||
private @Nullable String timezone;
|
||||
|
||||
private @Nullable String temperatureControlMode;
|
||||
private SetpointMode thermMode = SetpointMode.UNKNOWN;
|
||||
private int thermSetpointDefaultDuration;
|
||||
private List<ThermProgram> schedules = List.of();
|
||||
|
||||
private NAObjectMap<HomeDataPerson> persons = new NAObjectMap<>();
|
||||
private NAObjectMap<HomeDataRoom> rooms = new NAObjectMap<>();
|
||||
private NAObjectMap<HomeDataModule> modules = new NAObjectMap<>();
|
||||
|
||||
@Override
|
||||
public ModuleType getType() {
|
||||
return ModuleType.HOME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] getCoordinates() {
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getCountry() {
|
||||
return Optional.ofNullable(country);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getTimezone() {
|
||||
return Optional.ofNullable(timezone);
|
||||
}
|
||||
|
||||
public int getThermSetpointDefaultDuration() {
|
||||
return thermSetpointDefaultDuration;
|
||||
}
|
||||
|
||||
public SetpointMode getThermMode() {
|
||||
return thermMode;
|
||||
}
|
||||
|
||||
public NAObjectMap<HomeDataPerson> getPersons() {
|
||||
return persons;
|
||||
}
|
||||
|
||||
public List<HomeDataPerson> getKnownPersons() {
|
||||
return persons.values().stream().filter(HomeDataPerson::isKnown).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Optional<String> getTemperatureControlMode() {
|
||||
return Optional.ofNullable(temperatureControlMode);
|
||||
}
|
||||
|
||||
public NAObjectMap<HomeDataRoom> getRooms() {
|
||||
return rooms;
|
||||
}
|
||||
|
||||
public NAObjectMap<HomeDataModule> getModules() {
|
||||
return modules;
|
||||
}
|
||||
|
||||
public Set<FeatureArea> getFeatures() {
|
||||
return getModules().values().stream().map(m -> m.getType().feature).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public List<ThermProgram> getThermSchedules() {
|
||||
return schedules;
|
||||
}
|
||||
|
||||
public @Nullable ThermProgram getActiveProgram() {
|
||||
return schedules.stream().filter(ThermProgram::isSelected).findFirst().orElse(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link HomeDataModule} holds module informations returned by getHomeData endpoint
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class HomeDataModule extends NAThing implements NAModule {
|
||||
private @Nullable ZonedDateTime setupDate;
|
||||
private @Nullable String applianceType;
|
||||
private List<String> moduleBridged = List.of();
|
||||
|
||||
public @Nullable String getApplianceType() {
|
||||
return applianceType;
|
||||
}
|
||||
|
||||
public Optional<ZonedDateTime> getSetupDate() {
|
||||
return Optional.ofNullable(setupDate);
|
||||
}
|
||||
|
||||
public List<String> getModuleBridged() {
|
||||
return moduleBridged;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
|
||||
/**
|
||||
* The {@link HomeDataPerson} provides Person informations returned by getHomeData endpoint
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class HomeDataPerson extends NAThing implements NAModule {
|
||||
private @Nullable String url;
|
||||
|
||||
@Override
|
||||
public ModuleType getType() {
|
||||
return ModuleType.PERSON;
|
||||
}
|
||||
|
||||
public boolean isKnown() {
|
||||
return description != null;
|
||||
}
|
||||
|
||||
public Optional<String> getUrl() {
|
||||
return Optional.ofNullable(url);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
|
||||
/**
|
||||
* The {@link HomeDataRoom} provides Room informations returned by getHomeData endpoint
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomeDataRoom extends NAObject implements NAModule {
|
||||
private List<String> moduleIds = List.of();
|
||||
|
||||
@Override
|
||||
public ModuleType getType() {
|
||||
// In json api answer type for NARoom is used with free strings like kitchen, living...
|
||||
return ModuleType.ROOM;
|
||||
}
|
||||
|
||||
public List<String> getModuleIds() {
|
||||
return moduleIds;
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.ApiResponse;
|
||||
import org.openhab.binding.netatmo.internal.api.BodyResponse;
|
||||
import org.openhab.binding.netatmo.internal.api.data.EventSubType;
|
||||
import org.openhab.binding.netatmo.internal.api.data.EventType;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.EventCategory;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.VideoStatus;
|
||||
|
||||
/**
|
||||
* The {@link HomeEvent} holds information transferred by the webhook about a home event.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class HomeEvent extends Event {
|
||||
public class NAEventsDataResponse extends ApiResponse<BodyResponse<Home>> {
|
||||
}
|
||||
|
||||
private ZonedDateTime time = ZonedDateTime.now();
|
||||
private @Nullable String personId;
|
||||
private EventCategory category = EventCategory.UNKNOWN;
|
||||
private @Nullable Snapshot snapshot;
|
||||
private @Nullable String videoId;
|
||||
private VideoStatus videoStatus = VideoStatus.UNKNOWN;
|
||||
private boolean isArrival;
|
||||
|
||||
@Override
|
||||
public ZonedDateTime getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getPersonId() {
|
||||
return personId;
|
||||
}
|
||||
|
||||
public @Nullable String getVideoId() {
|
||||
return videoId;
|
||||
}
|
||||
|
||||
public VideoStatus getVideoStatus() {
|
||||
return videoStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<EventSubType> getSubTypeDescription() {
|
||||
// Blend extra information provided by this kind of event in subcategories...
|
||||
if (type == EventType.PERSON) {
|
||||
subType = isArrival ? EventSubType.PERSON_ARRIVAL.subType : EventSubType.PERSON_SEEN.subType;
|
||||
} else if (type == EventType.PERSON_HOME) {
|
||||
subType = EventSubType.PERSON_ARRIVAL.subType;
|
||||
} else if (type == EventType.PERSON_AWAY) {
|
||||
subType = EventSubType.PERSON_DEPARTURE.subType;
|
||||
} else if (type == EventType.HUMAN) {
|
||||
subType = EventSubType.MOVEMENT_HUMAN.subType;
|
||||
} else if (type == EventType.ANIMAL) {
|
||||
subType = EventSubType.MOVEMENT_ANIMAL.subType;
|
||||
} else {
|
||||
if (category == EventCategory.ANIMAL) {
|
||||
subType = EventSubType.MOVEMENT_ANIMAL.subType;
|
||||
} else if (category == EventCategory.HUMAN) {
|
||||
subType = EventSubType.MOVEMENT_HUMAN.subType;
|
||||
} else if (category == EventCategory.VEHICLE) {
|
||||
subType = EventSubType.MOVEMENT_VEHICLE.subType;
|
||||
}
|
||||
}
|
||||
// ... and let ancestor do his work
|
||||
return super.getSubTypeDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getSnapshotUrl() {
|
||||
Snapshot localSnap = snapshot;
|
||||
return localSnap != null ? localSnap.getUrl() : null;
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.AlimentationStatus;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.BatteryState;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SdCardStatus;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link HomeStatusModule} holds module informations returned by getHomeData endpoint
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class HomeStatusModule extends NAThing {
|
||||
private @Nullable String firmwareName;
|
||||
private @Nullable String wifiState;
|
||||
private @Nullable String status;
|
||||
private @Nullable OnOffType monitoring;
|
||||
private FloodLightMode floodlight = FloodLightMode.UNKNOWN;
|
||||
private SdCardStatus sdStatus = SdCardStatus.UNKNOWN;
|
||||
private AlimentationStatus alimStatus = AlimentationStatus.UNKNOWN;
|
||||
private @Nullable String sirenStatus;
|
||||
private @Nullable String vpnUrl;
|
||||
private boolean isLocal;
|
||||
private BatteryState batteryState = BatteryState.UNKNOWN;
|
||||
private int batteryLevel;
|
||||
|
||||
private @Nullable OpenClosedType boilerStatus;
|
||||
private boolean boilerValveComfortBoost;
|
||||
|
||||
public State getBoilerStatus() {
|
||||
OpenClosedType status = boilerStatus;
|
||||
return status != null ? status : UnDefType.NULL;
|
||||
}
|
||||
|
||||
public boolean getBoilerValveComfortBoost() {
|
||||
return boilerValveComfortBoost;
|
||||
}
|
||||
|
||||
public Optional<String> getFirmwareName() {
|
||||
return Optional.ofNullable(firmwareName);
|
||||
}
|
||||
|
||||
public Optional<String> getWifiState() {
|
||||
return Optional.ofNullable(wifiState);
|
||||
}
|
||||
|
||||
public Optional<String> getStatus() {
|
||||
return Optional.ofNullable(status);
|
||||
}
|
||||
|
||||
public State getMonitoring() {
|
||||
OnOffType localStatus = monitoring;
|
||||
return localStatus != null ? localStatus : UnDefType.NULL;
|
||||
}
|
||||
|
||||
public FloodLightMode getFloodlight() {
|
||||
return floodlight;
|
||||
}
|
||||
|
||||
public SdCardStatus getSdStatus() {
|
||||
return sdStatus;
|
||||
}
|
||||
|
||||
public AlimentationStatus getAlimStatus() {
|
||||
return alimStatus;
|
||||
}
|
||||
|
||||
public Optional<String> getSirenStatus() {
|
||||
return Optional.ofNullable(sirenStatus);
|
||||
}
|
||||
|
||||
public @Nullable String getVpnUrl() {
|
||||
return vpnUrl;
|
||||
}
|
||||
|
||||
public boolean isLocal() {
|
||||
return isLocal;
|
||||
}
|
||||
|
||||
public BatteryState getBatteryState() {
|
||||
return batteryState;
|
||||
}
|
||||
|
||||
public int getBatteryLevel() {
|
||||
return batteryLevel;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
|
||||
/**
|
||||
* The {@link HomeStatusPerson} provides Person informations returned by getHomeData endpoint
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class HomeStatusPerson extends NAThing {
|
||||
private boolean outOfSight;
|
||||
|
||||
@Override
|
||||
public ModuleType getType() {
|
||||
return ModuleType.PERSON;
|
||||
}
|
||||
|
||||
public boolean isOutOfSight() {
|
||||
return outOfSight;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link Location} is the common interface for dto holding a location
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface Location {
|
||||
double[] getCoordinates();
|
||||
|
||||
double getAltitude();
|
||||
|
||||
default State getLocation() {
|
||||
double[] coordinates = getCoordinates();
|
||||
return coordinates.length != 2 ? UnDefType.UNDEF
|
||||
: new PointType(new DecimalType(coordinates[1]), new DecimalType(coordinates[0]),
|
||||
new DecimalType(getAltitude()));
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link LocationEx} is the common interface for dto holding a extra location data
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface LocationEx extends Location {
|
||||
public Optional<String> getCountry();
|
||||
|
||||
public Optional<String> getTimezone();
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link MeasureBodyElem} holds a list of values returned by getMeasure endpoint.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class MeasureBodyElem<T> {
|
||||
private List<List<T>> value = List.of();
|
||||
|
||||
public List<List<T>> getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public @Nullable T getSingleValue() {
|
||||
if (!value.isEmpty()) {
|
||||
List<T> first = value.get(0);
|
||||
if (!first.isEmpty()) {
|
||||
return first.get(0);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.BatteryState;
|
||||
|
||||
/**
|
||||
* The {@link Module} holds status information of a Netatmo module.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class Module extends NAThing {
|
||||
private BatteryState batteryState = BatteryState.UNKNOWN;
|
||||
private int batteryPercent = -1;
|
||||
|
||||
public int getBatteryPercent() {
|
||||
return batteryPercent != -1 ? batteryPercent : batteryState.level;
|
||||
}
|
||||
|
||||
public BatteryState getBatteryState() {
|
||||
return batteryState;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.ApiResponse;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
|
||||
/**
|
||||
* The {@link NAHomeStatus} holds data for a given home.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAHomeStatus {
|
||||
public class NAHomeStatusResponse extends ApiResponse<NAHomeStatus> {
|
||||
}
|
||||
|
||||
public class HomeStatus extends NAThing {
|
||||
private @Nullable NAObjectMap<HomeStatusModule> modules;
|
||||
private @Nullable NAObjectMap<HomeStatusPerson> persons;
|
||||
private @Nullable NAObjectMap<Room> rooms;
|
||||
|
||||
public NAObjectMap<HomeStatusModule> getModules() {
|
||||
NAObjectMap<HomeStatusModule> localModules = modules;
|
||||
return localModules != null ? localModules : new NAObjectMap<>();
|
||||
}
|
||||
|
||||
public NAObjectMap<HomeStatusPerson> getPersons() {
|
||||
NAObjectMap<HomeStatusPerson> localPersons = persons;
|
||||
return localPersons != null ? localPersons : new NAObjectMap<>();
|
||||
}
|
||||
|
||||
public NAObjectMap<Room> getRooms() {
|
||||
NAObjectMap<Room> localRooms = rooms;
|
||||
return localRooms != null ? localRooms : new NAObjectMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable HomeStatus home;
|
||||
|
||||
public Optional<HomeStatus> getHomeStatus() {
|
||||
return Optional.ofNullable(home);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.ApiResponse;
|
||||
import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
|
||||
|
||||
/**
|
||||
* The {@link NAMain} defines a weather or nhc device.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class NAMain extends Device {
|
||||
public class StationDataResponse extends ApiResponse<ListBodyResponse<NAMain>> {
|
||||
}
|
||||
|
||||
private boolean readOnly;
|
||||
|
||||
/**
|
||||
* true when the user was invited to (or has favorited) a station, false when the user owns it
|
||||
*
|
||||
* @return readOnly
|
||||
**/
|
||||
public boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
public boolean hasFreshData(int dataFreshnessLimit) {
|
||||
// check by comparing data freshness
|
||||
ZonedDateTime localLastSeen = lastSeen;
|
||||
if (localLastSeen != null && !getType().isLogical()) {
|
||||
return Duration.between(localLastSeen.toInstant(), Instant.now()).getSeconds() < dataFreshnessLimit;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
|
||||
/**
|
||||
* The {@link NAModule} is the common interface for dto holding module informations
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface NAModule {
|
||||
public String getId();
|
||||
|
||||
public @Nullable String getName();
|
||||
|
||||
public ModuleType getType();
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link NAObject} class is the base class for all objects
|
||||
* returned by the Netatmo API.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAObject {
|
||||
@SerializedName(value = "id", alternate = { "program_id", "_id", "event_id" })
|
||||
protected String id = "";
|
||||
@SerializedName(value = "name", alternate = { "module_name", "station_name", "pseudo", "message", "key" })
|
||||
protected @Nullable String description;
|
||||
private boolean ignoredForThingUpdate;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public @Nullable String getName() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean isIgnoredForThingUpdate() {
|
||||
return ignoredForThingUpdate;
|
||||
}
|
||||
|
||||
public void setIgnoredForThingUpdate(boolean ignoredForThingUpdate) {
|
||||
this.ignoredForThingUpdate = ignoredForThingUpdate;
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link NAThing} is the base class for devices and modules.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class NAThing extends NAObject implements NAModule {
|
||||
@SerializedName(value = "rf_status", alternate = { "wifi_status", "rf_strength", "wifi_strength" })
|
||||
private int radioStatus = -1;
|
||||
@SerializedName(value = "last_seen", alternate = { "last_therm_seen", "last_status_store", "last_plug_seen",
|
||||
"last_message", "last_activity" })
|
||||
protected @Nullable ZonedDateTime lastSeen;
|
||||
@SerializedName(value = "firmware", alternate = { "firmware_revision" })
|
||||
private @Nullable String firmware;
|
||||
private @Nullable Boolean reachable;
|
||||
private @Nullable Dashboard dashboardData;
|
||||
|
||||
private @Nullable String roomId;
|
||||
private @Nullable String bridge;
|
||||
private ModuleType type = ModuleType.UNKNOWN;
|
||||
|
||||
@Override
|
||||
public ModuleType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public boolean isReachable() {
|
||||
// This is not implemented on all devices/modules, so if absent we consider it is reachable
|
||||
Boolean localReachable = this.reachable;
|
||||
return localReachable != null ? localReachable : true;
|
||||
}
|
||||
|
||||
public void setReachable(boolean reachable) {
|
||||
this.reachable = reachable;
|
||||
}
|
||||
|
||||
public @Nullable Dashboard getDashboardData() {
|
||||
return dashboardData;
|
||||
}
|
||||
|
||||
public @Nullable String getFirmware() {
|
||||
return firmware;
|
||||
}
|
||||
|
||||
public int getRadioStatus() {
|
||||
return radioStatus;
|
||||
}
|
||||
|
||||
public Optional<ZonedDateTime> getLastSeen() {
|
||||
return Optional.ofNullable(lastSeen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the equipment has no parent, meaning its a device.
|
||||
*/
|
||||
public boolean isDevice() {
|
||||
return bridge == null;
|
||||
}
|
||||
|
||||
public @Nullable String getBridge() {
|
||||
return bridge;
|
||||
}
|
||||
|
||||
public @Nullable String getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
|
||||
/**
|
||||
* The {@link Person} holds answers provided in webhook events
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class Person extends NAThing {
|
||||
private @Nullable String faceUrl;
|
||||
private boolean isKnown;
|
||||
|
||||
@Override
|
||||
public ModuleType getType() {
|
||||
return ModuleType.PERSON;
|
||||
}
|
||||
|
||||
public @Nullable String getFaceUrl() {
|
||||
return faceUrl;
|
||||
}
|
||||
|
||||
public boolean isKnown() {
|
||||
return isKnown;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.ApiResponse;
|
||||
|
||||
/**
|
||||
* The {@link Ping} hold url data for a camera module
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ping extends ApiResponse<String> {
|
||||
private String localUrl = "";
|
||||
private @Nullable String productName;
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return localUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getBody() {
|
||||
return productName;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link Place} reports location information of a Netatmo system.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class Place implements LocationEx {
|
||||
private @Nullable String city;
|
||||
private @Nullable String country;
|
||||
private @Nullable String timezone;
|
||||
private double altitude;
|
||||
private double[] location = {};
|
||||
|
||||
public Optional<String> getCity() {
|
||||
return Optional.ofNullable(city);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getCountry() {
|
||||
return Optional.ofNullable(country);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getTimezone() {
|
||||
return Optional.ofNullable(timezone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] getCoordinates() {
|
||||
return location;
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link Room} holds temperature data for a given room.
|
||||
*
|
||||
* @author Bernhard Kreuz - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Room extends NAObject implements NAModule {
|
||||
private @Nullable String type;
|
||||
private @Nullable OnOffType anticipating;
|
||||
private boolean openWindow;
|
||||
private @Nullable ZonedDateTime thermSetpointStartTime;
|
||||
private @Nullable ZonedDateTime thermSetpointEndTime;
|
||||
private SetpointMode thermSetpointMode = SetpointMode.UNKNOWN;
|
||||
private int heatingPowerRequest;
|
||||
private double thermMeasuredTemperature;
|
||||
private double thermSetpointTemperature;
|
||||
|
||||
public State isAnticipating() {
|
||||
OnOffType status = anticipating;
|
||||
return status != null ? status : UnDefType.NULL;
|
||||
}
|
||||
|
||||
public State hasOpenedWindows() {
|
||||
return openWindow ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
|
||||
}
|
||||
|
||||
public int getHeatingPowerRequest() {
|
||||
return heatingPowerRequest;
|
||||
}
|
||||
|
||||
public double getMeasuredTemp() {
|
||||
return thermMeasuredTemperature;
|
||||
}
|
||||
|
||||
public SetpointMode getSetpointMode() {
|
||||
return thermSetpointMode;
|
||||
}
|
||||
|
||||
public double getSetpointTemp() {
|
||||
return thermSetpointTemperature;
|
||||
}
|
||||
|
||||
public @Nullable ZonedDateTime getSetpointBegin() {
|
||||
return thermSetpointStartTime;
|
||||
}
|
||||
|
||||
public @Nullable ZonedDateTime getSetpointEnd() {
|
||||
return thermSetpointEndTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleType getType() {
|
||||
// Note: In json api answer type for NARoom is used with words like kitchen, living...
|
||||
return ModuleType.ROOM;
|
||||
}
|
||||
|
||||
public @Nullable String getLocation() {
|
||||
return type;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link Snapshot} holds data related to a snapshot.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class Snapshot {
|
||||
private @Nullable String url;
|
||||
|
||||
public @Nullable String getUrl() {
|
||||
return url;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
|
||||
/**
|
||||
* The {@link ThermProgram} holds setpoint scheduling information.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class ThermProgram extends NAObject {
|
||||
private NAObjectMap<Zone> zones = new NAObjectMap<>();
|
||||
private List<TimeTableItem> timetable = List.of();
|
||||
private boolean selected;
|
||||
|
||||
public List<TimeTableItem> getTimetable() {
|
||||
return timetable;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public @Nullable Zone getZone(String id) {
|
||||
return zones.get(id);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link TimeTableItem} holds the temp scheduling for a given zone.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class TimeTableItem extends NAObject {
|
||||
private int mOffset;
|
||||
private int zoneId;
|
||||
|
||||
public int getMinuteOffset() {
|
||||
return mOffset;
|
||||
}
|
||||
|
||||
public int getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.EventType;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAPushType;
|
||||
|
||||
/**
|
||||
* The {@link WebhookEvent} is responsible to hold
|
||||
* data given back by the Netatmo API when calling the webhook
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WebhookEvent extends Event {
|
||||
private @NonNullByDefault({}) NAPushType pushType;
|
||||
private String homeId = "";
|
||||
private @Nullable String snapshotUrl;
|
||||
private NAObjectMap<Person> persons = new NAObjectMap<>();
|
||||
// Webhook does not provide the event generation time, so we'll use the event reception time
|
||||
private ZonedDateTime time = ZonedDateTime.now();
|
||||
|
||||
public String getHomeId() {
|
||||
return homeId;
|
||||
}
|
||||
|
||||
public NAObjectMap<Person> getPersons() {
|
||||
return persons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventType getEventType() {
|
||||
return pushType.getEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getPersonId() {
|
||||
return persons.size() > 0 ? persons.keySet().iterator().next() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getSnapshotUrl() {
|
||||
return snapshotUrl;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ThermostatZoneType;
|
||||
|
||||
/**
|
||||
* The {@link Zone} holds temperature data for a given zone.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class Zone extends NAObject {
|
||||
private ThermostatZoneType type = ThermostatZoneType.UNKNOWN;
|
||||
private double temp;
|
||||
|
||||
public double getTemp() {
|
||||
return temp;
|
||||
}
|
||||
|
||||
public ThermostatZoneType getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.camera;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* {@link CameraAddress} handles the data to address a camera (VPN and local address).
|
||||
*
|
||||
* @author Sven Strohschein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CameraAddress {
|
||||
|
||||
private final String vpnURL;
|
||||
private final String localURL;
|
||||
|
||||
CameraAddress(final String vpnURL, final String localURL) {
|
||||
this.vpnURL = vpnURL;
|
||||
this.localURL = localURL;
|
||||
}
|
||||
|
||||
public String getVpnURL() {
|
||||
return vpnURL;
|
||||
}
|
||||
|
||||
public String getLocalURL() {
|
||||
return localURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the VPN URL was changed / isn't equal to the given VPN-URL.
|
||||
*
|
||||
* @param vpnURL old / known VPN URL
|
||||
* @return true, when the VPN URL isn't equal given VPN URL, otherwise false
|
||||
*/
|
||||
public boolean isVpnURLChanged(String vpnURL) {
|
||||
return !getVpnURL().equals(vpnURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (object == null || getClass() != object.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CameraAddress that = (CameraAddress) object;
|
||||
return vpnURL.equals(that.vpnURL) && localURL.equals(that.localURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(vpnURL, localURL);
|
||||
}
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.camera;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.swagger.client.model.NAWelcomeCamera;
|
||||
|
||||
/**
|
||||
* {@link CameraHandler} is the class used to handle Camera Data
|
||||
*
|
||||
* @author Sven Strohschein - Initial contribution (partly moved code from NAWelcomeCameraHandler to introduce
|
||||
* inheritance, see NAWelcomeCameraHandler)
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class CameraHandler extends NetatmoModuleHandler<NAWelcomeCamera> {
|
||||
|
||||
private static final String PING_URL_PATH = "/command/ping";
|
||||
private static final String STATUS_CHANGE_URL_PATH = "/command/changestatus";
|
||||
private static final String LIVE_PICTURE = "/live/snapshot_720.jpg";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CameraHandler.class);
|
||||
|
||||
private Optional<CameraAddress> cameraAddress;
|
||||
|
||||
protected CameraHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
cameraAddress = Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
String channelId = channelUID.getId();
|
||||
switch (channelId) {
|
||||
case CHANNEL_CAMERA_STATUS:
|
||||
case CHANNEL_WELCOME_CAMERA_STATUS:
|
||||
if (command == OnOffType.ON) {
|
||||
switchVideoSurveillance(true);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
switchVideoSurveillance(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
super.handleCommand(channelUID, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProperties(NAWelcomeCamera moduleData) {
|
||||
updateProperties(null, moduleData.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_CAMERA_STATUS:
|
||||
return getStatusState();
|
||||
case CHANNEL_CAMERA_SDSTATUS:
|
||||
return getSdStatusState();
|
||||
case CHANNEL_CAMERA_ALIMSTATUS:
|
||||
return getAlimStatusState();
|
||||
case CHANNEL_CAMERA_ISLOCAL:
|
||||
return getIsLocalState();
|
||||
case CHANNEL_CAMERA_LIVEPICTURE_URL:
|
||||
return getLivePictureURLState();
|
||||
case CHANNEL_CAMERA_LIVEPICTURE:
|
||||
return getLivePictureState();
|
||||
case CHANNEL_CAMERA_LIVESTREAM_URL:
|
||||
return getLiveStreamState();
|
||||
}
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
protected State getStatusState() {
|
||||
return getModule().map(m -> toOnOffType(m.getStatus())).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
protected State getSdStatusState() {
|
||||
return getModule().map(m -> toOnOffType(m.getSdStatus())).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
protected State getAlimStatusState() {
|
||||
return getModule().map(m -> toOnOffType(m.getAlimStatus())).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
protected State getIsLocalState() {
|
||||
return getModule().map(m -> toOnOffType(m.isIsLocal())).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
protected State getLivePictureURLState() {
|
||||
return getLivePictureURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
protected State getLivePictureState() {
|
||||
Optional<String> livePictureURL = getLivePictureURL();
|
||||
return livePictureURL.isPresent() ? toRawType(livePictureURL.get()) : UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
protected State getLiveStreamState() {
|
||||
return getLiveStreamURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url for the live snapshot
|
||||
*
|
||||
* @return Url of the live snapshot
|
||||
*/
|
||||
private Optional<String> getLivePictureURL() {
|
||||
return getVpnUrl().map(u -> u += LIVE_PICTURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url for the live stream depending wether local or not
|
||||
*
|
||||
* @return Url of the live stream
|
||||
*/
|
||||
private Optional<String> getLiveStreamURL() {
|
||||
Optional<String> result = getVpnUrl();
|
||||
if (!result.isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
StringBuilder resultStringBuilder = new StringBuilder(result.get());
|
||||
resultStringBuilder.append("/live/index");
|
||||
if (isLocal()) {
|
||||
resultStringBuilder.append("_local");
|
||||
}
|
||||
resultStringBuilder.append(".m3u8");
|
||||
return Optional.of(resultStringBuilder.toString());
|
||||
}
|
||||
|
||||
private Optional<String> getVpnUrl() {
|
||||
return getModule().map(NAWelcomeCamera::getVpnUrl);
|
||||
}
|
||||
|
||||
public Optional<String> getStreamURL(String videoId) {
|
||||
Optional<String> result = getVpnUrl();
|
||||
if (!result.isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
StringBuilder resultStringBuilder = new StringBuilder(result.get());
|
||||
resultStringBuilder.append("/vod/");
|
||||
resultStringBuilder.append(videoId);
|
||||
resultStringBuilder.append("/index");
|
||||
if (isLocal()) {
|
||||
resultStringBuilder.append("_local");
|
||||
}
|
||||
resultStringBuilder.append(".m3u8");
|
||||
return Optional.of(resultStringBuilder.toString());
|
||||
}
|
||||
|
||||
private boolean isLocal() {
|
||||
return getModule().map(NAWelcomeCamera::isIsLocal).orElse(false);
|
||||
}
|
||||
|
||||
private void switchVideoSurveillance(boolean isOn) {
|
||||
Optional<String> localCameraURL = getLocalCameraURL();
|
||||
if (localCameraURL.isPresent()) {
|
||||
String url = localCameraURL.get() + STATUS_CHANGE_URL_PATH + "?status=";
|
||||
if (isOn) {
|
||||
url += "on";
|
||||
} else {
|
||||
url += "off";
|
||||
}
|
||||
executeGETRequest(url);
|
||||
|
||||
invalidateParentCacheAndRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
protected Optional<String> getLocalCameraURL() {
|
||||
Optional<String> vpnURLOptional = getVpnUrl();
|
||||
Optional<CameraAddress> address = cameraAddress;
|
||||
if (vpnURLOptional.isPresent()) {
|
||||
final String vpnURL = vpnURLOptional.get();
|
||||
|
||||
// The local address is (re-)requested when it wasn't already determined or when the vpn address was
|
||||
// changed.
|
||||
if (!address.isPresent() || address.get().isVpnURLChanged(vpnURL)) {
|
||||
Optional<JSONObject> json = executeGETRequestJSON(vpnURL + PING_URL_PATH);
|
||||
address = json.map(j -> j.optString("local_url", null))
|
||||
.map(localURL -> new CameraAddress(vpnURL, localURL));
|
||||
cameraAddress = address;
|
||||
}
|
||||
}
|
||||
return address.map(CameraAddress::getLocalURL);
|
||||
}
|
||||
|
||||
private Optional<JSONObject> executeGETRequestJSON(String url) {
|
||||
try {
|
||||
return executeGETRequest(url).map(JSONObject::new);
|
||||
} catch (JSONException e) {
|
||||
logger.warn("Error on parsing the content as JSON!", e);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
protected Optional<String> executeGETRequest(String url) {
|
||||
try {
|
||||
String content = HttpUtil.executeUrl("GET", url, 5000);
|
||||
if (content != null && !content.isEmpty()) {
|
||||
return Optional.of(content);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error on accessing local camera url!", e);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReachable() {
|
||||
Optional<NAWelcomeCamera> module = getModule();
|
||||
return module.isPresent() ? !"disconnected".equalsIgnoreCase(module.get().getStatus()) : false;
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.channelhelper;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link BatteryHelper} handle specific behavior
|
||||
* of modules using batteries
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BatteryHelper {
|
||||
private final Logger logger = LoggerFactory.getLogger(BatteryHelper.class);
|
||||
private int batteryLow;
|
||||
|
||||
private @Nullable Object module;
|
||||
|
||||
public BatteryHelper(String batteryLevels) {
|
||||
List<String> thresholds = Arrays.asList(batteryLevels.split(","));
|
||||
batteryLow = Integer.parseInt(thresholds.get(1));
|
||||
}
|
||||
|
||||
public void setModule(Object module) {
|
||||
this.module = module;
|
||||
}
|
||||
|
||||
public Optional<State> getNAThingProperty(String channelId) {
|
||||
Object module = this.module;
|
||||
if (module != null) {
|
||||
try {
|
||||
if (CHANNEL_BATTERY_LEVEL.equalsIgnoreCase(channelId)
|
||||
|| CHANNEL_LOW_BATTERY.equalsIgnoreCase(channelId)) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_BATTERY_LEVEL:
|
||||
Method getBatteryPercent = module.getClass().getMethod("getBatteryPercent");
|
||||
Integer batteryPercent = (Integer) getBatteryPercent.invoke(module);
|
||||
return Optional.of(ChannelTypeUtils.toDecimalType(batteryPercent));
|
||||
case CHANNEL_LOW_BATTERY:
|
||||
Method getBatteryVp = module.getClass().getMethod("getBatteryVp");
|
||||
Integer batteryVp = (Integer) getBatteryVp.invoke(module);
|
||||
return Optional.of(batteryVp < batteryLow ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException e) {
|
||||
logger.warn("The module has no method to access {} property : {}", channelId, e.getMessage());
|
||||
return Optional.of(UnDefType.NULL);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.channelhelper;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RadioHelper} handle specific behavior
|
||||
* of WIFI or RF devices and modules
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RadioHelper {
|
||||
private final Logger logger = LoggerFactory.getLogger(RadioHelper.class);
|
||||
private final List<Integer> signalThresholds;
|
||||
private @Nullable Object module;
|
||||
|
||||
public RadioHelper(String signalLevels) {
|
||||
signalThresholds = Stream.of(signalLevels.split(",")).map(Integer::parseInt).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private int getSignalStrength(int signalLevel) {
|
||||
int level;
|
||||
for (level = 0; level < signalThresholds.size(); level++) {
|
||||
if (signalLevel > signalThresholds.get(level)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setModule(Object module) {
|
||||
this.module = module;
|
||||
}
|
||||
|
||||
public Optional<State> getNAThingProperty(String channelId) {
|
||||
Object module = this.module;
|
||||
if (module != null) {
|
||||
try {
|
||||
switch (channelId) {
|
||||
case CHANNEL_RF_STATUS:
|
||||
Method getRfStatus = module.getClass().getMethod("getRfStatus");
|
||||
Integer rfStatus = (Integer) getRfStatus.invoke(module);
|
||||
return Optional.of(new DecimalType(getSignalStrength(rfStatus)));
|
||||
case CHANNEL_WIFI_STATUS:
|
||||
Method getWifiStatus = module.getClass().getMethod("getWifiStatus");
|
||||
Integer wifiStatus = (Integer) getWifiStatus.invoke(module);
|
||||
return Optional.of(new DecimalType(getSignalStrength(wifiStatus)));
|
||||
}
|
||||
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException e) {
|
||||
logger.warn("The module has no method to access {} property : {}", channelId, e.getMessage());
|
||||
return Optional.of(UnDefType.NULL);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.NetatmoException;
|
||||
|
||||
/**
|
||||
* The {@link ApiHandlerConfiguration} is responsible for holding configuration
|
||||
* information needed to access Netatmo API and general binding behavior setup
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ApiHandlerConfiguration {
|
||||
public class Credentials {
|
||||
public final String clientId, clientSecret, username, password;
|
||||
|
||||
private Credentials(@Nullable String clientId, @Nullable String clientSecret, @Nullable String username,
|
||||
@Nullable String password) throws NetatmoException {
|
||||
this.clientSecret = checkMandatory(clientSecret, "@text/conf-error-no-client-secret");
|
||||
this.username = checkMandatory(username, "@text/conf-error-no-username");
|
||||
this.password = checkMandatory(password, "@text/conf-error-no-password");
|
||||
this.clientId = checkMandatory(clientId, "@text/conf-error-no-client-id");
|
||||
}
|
||||
|
||||
private String checkMandatory(@Nullable String value, String error) throws NetatmoException {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new NetatmoException(error);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Credentials [clientId=" + clientId + ", username=" + username
|
||||
+ ", password=******, clientSecret=******]";
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable String clientId;
|
||||
private @Nullable String clientSecret;
|
||||
private @Nullable String username;
|
||||
private @Nullable String password;
|
||||
public @Nullable String webHookUrl;
|
||||
public int reconnectInterval = 300;
|
||||
|
||||
public Credentials getCredentials() throws NetatmoException {
|
||||
return new Credentials(clientId, clientSecret, username, password);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.config;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
|
||||
/**
|
||||
* The {@link BindingConfiguration} is responsible for holding configuration of the binding itself.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BindingConfiguration {
|
||||
public Set<FeatureArea> features = Set.of();
|
||||
public boolean readFriends = false;
|
||||
|
||||
public void update(BindingConfiguration newConfig) {
|
||||
this.features = newConfig.features;
|
||||
this.readFriends = newConfig.readFriends;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link MeasureConfiguration} is responsible for holding
|
||||
* configuration information for measure channels
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MeasureConfiguration {
|
||||
public String period = "";
|
||||
public String limit = "";
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link NAThingConfiguration} is responsible for holding
|
||||
* configuration information for any Netatmo thing module or device
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAThingConfiguration {
|
||||
public String id = "";
|
||||
public int refreshInterval = -1;
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link NetatmoBridgeConfiguration} is responsible for holding
|
||||
* configuration informations needed to access Netatmo API
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NetatmoBridgeConfiguration {
|
||||
public @Nullable String clientId;
|
||||
public @Nullable String clientSecret;
|
||||
public @Nullable String username;
|
||||
public @Nullable String password;
|
||||
public boolean readStation = true;
|
||||
public boolean readThermostat = false;
|
||||
public boolean readHealthyHomeCoach = false;
|
||||
public boolean readWelcome = false;
|
||||
public boolean readPresence = false;
|
||||
public @Nullable String webHookUrl;
|
||||
public int reconnectInterval = 5400;
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.deserialization;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.NetatmoException;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link NADeserializer} is responsible to instantiate suitable Gson (de)serializer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = NADeserializer.class)
|
||||
public class NADeserializer {
|
||||
private final Gson gson;
|
||||
|
||||
@Activate
|
||||
public NADeserializer(@Reference TimeZoneProvider timeZoneProvider) {
|
||||
gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory())
|
||||
.registerTypeAdapter(NAObjectMap.class, new NAObjectMapDeserializer())
|
||||
.registerTypeAdapter(NAPushType.class, new NAPushTypeDeserializer())
|
||||
.registerTypeAdapter(ZonedDateTime.class,
|
||||
(JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
|
||||
long netatmoTS = json.getAsJsonPrimitive().getAsLong();
|
||||
Instant i = Instant.ofEpochSecond(netatmoTS);
|
||||
return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone());
|
||||
})
|
||||
.registerTypeAdapter(OnOffType.class,
|
||||
(JsonDeserializer<OnOffType>) (json, type, jsonDeserializationContext) -> OnOffType
|
||||
.from(json.getAsJsonPrimitive().getAsString()))
|
||||
.registerTypeAdapter(OpenClosedType.class,
|
||||
(JsonDeserializer<OpenClosedType>) (json, type, jsonDeserializationContext) -> {
|
||||
String value = json.getAsJsonPrimitive().getAsString().toUpperCase();
|
||||
return "TRUE".equals(value) || "1".equals(value) ? OpenClosedType.CLOSED
|
||||
: OpenClosedType.OPEN;
|
||||
})
|
||||
.create();
|
||||
}
|
||||
|
||||
public <T> T deserialize(Class<T> clazz, String json) throws NetatmoException {
|
||||
try {
|
||||
@Nullable
|
||||
T result = gson.fromJson(json, clazz);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
throw new NetatmoException("Deserialization of '%s' resulted in null value", json);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new NetatmoException(e, "Unexpected error deserializing '%s'", json);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.deserialization;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
|
||||
/**
|
||||
* The {@link NAObjectMap} defines a hashmap of NAObjects identified by their id.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAObjectMap<T extends NAObject> extends HashMap<String, T> {
|
||||
private static final long serialVersionUID = 7635233672795516649L;
|
||||
|
||||
@Nullable
|
||||
public T put(T thing) {
|
||||
return super.put(thing.getId(), thing);
|
||||
}
|
||||
|
||||
public Optional<T> getOpt(String key) {
|
||||
return Optional.ofNullable(super.get(key));
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.deserialization;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* The {@link NAObjectMapDeserializer} is a specialized deserializer aimed to transform
|
||||
* a list of `NAObjects` into a map identified by the object's id.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class NAObjectMapDeserializer implements JsonDeserializer<NAObjectMap<?>> {
|
||||
@Override
|
||||
public @Nullable NAObjectMap<?> deserialize(JsonElement json, Type clazz, JsonDeserializationContext context)
|
||||
throws JsonParseException {
|
||||
ParameterizedType parameterized = (ParameterizedType) clazz;
|
||||
Type[] typeArguments = parameterized.getActualTypeArguments();
|
||||
if (typeArguments.length > 0 && json instanceof JsonArray) {
|
||||
Type objectType = typeArguments[0];
|
||||
NAObjectMap<NAObject> result = new NAObjectMap<>();
|
||||
((JsonArray) json).forEach(item -> {
|
||||
result.put(context.deserialize(item, objectType));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.deserialization;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.EventType;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
|
||||
/**
|
||||
* This class holds data of push_type field
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAPushType {
|
||||
private final ModuleType moduleType;
|
||||
private final EventType event;
|
||||
|
||||
NAPushType(ModuleType moduleType, EventType event) {
|
||||
this.moduleType = moduleType;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public ModuleType getModuleType() {
|
||||
return moduleType;
|
||||
}
|
||||
|
||||
public EventType getEvent() {
|
||||
return event;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.deserialization;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.EventType;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* Specialized deserializer for push_type field
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class NAPushTypeDeserializer implements JsonDeserializer<NAPushType> {
|
||||
|
||||
@Override
|
||||
public @Nullable NAPushType deserialize(JsonElement json, Type clazz, JsonDeserializationContext context)
|
||||
throws JsonParseException {
|
||||
String string = json.getAsString();
|
||||
String[] elements = string.split("-");
|
||||
if (elements.length > 1) {
|
||||
try {
|
||||
ModuleType moduleType = ModuleType.from(elements[0]);
|
||||
EventType eventType = EventType.valueOf(elements[1].toUpperCase());
|
||||
|
||||
return new NAPushType(moduleType, eventType);
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
}
|
||||
throw new JsonParseException("Error deserializing : " + string);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.deserialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* Enforces a fallback to UNKNOWN when deserializing enum types, marked as @NonNull whereas they were valued
|
||||
* to null if the appropriate value is absent. It will give more resilience to the binding when Netatmo API evolves.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class StrictEnumTypeAdapterFactory implements TypeAdapterFactory {
|
||||
private static final StringReader UNKNOWN = new StringReader("\"UNKNOWN\"");
|
||||
|
||||
@Override
|
||||
public @Nullable <T> TypeAdapter<T> create(@NonNullByDefault({}) Gson gson,
|
||||
@NonNullByDefault({}) TypeToken<T> type) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> rawType = (Class<T>) type.getRawType();
|
||||
return rawType.isEnum() ? newStrictEnumAdapter(gson.getDelegateAdapter(this, type)) : null;
|
||||
}
|
||||
|
||||
private <T> TypeAdapter<T> newStrictEnumAdapter(@NonNullByDefault({}) TypeAdapter<T> delegateAdapter) {
|
||||
return new TypeAdapter<T>() {
|
||||
@Override
|
||||
public void write(JsonWriter out, @Nullable T value) throws IOException {
|
||||
delegateAdapter.write(out, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T read(JsonReader in) throws IOException {
|
||||
JsonReader delegateReader = new JsonReader(new StringReader('"' + in.nextString() + '"'));
|
||||
@Nullable
|
||||
T value = delegateAdapter.read(delegateReader);
|
||||
delegateReader.close();
|
||||
if (value == null) {
|
||||
UNKNOWN.reset();
|
||||
value = delegateAdapter.read(new JsonReader(UNKNOWN));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.NetatmoBindingConstants.EQUIPMENT_ID;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.AircareApi;
|
||||
import org.openhab.binding.netatmo.internal.api.HomeApi;
|
||||
import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
|
||||
import org.openhab.binding.netatmo.internal.api.NetatmoException;
|
||||
import org.openhab.binding.netatmo.internal.api.WeatherApi;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAMain;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAModule;
|
||||
import org.openhab.binding.netatmo.internal.config.BindingConfiguration;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NetatmoDiscoveryService} searches for available Netatmo things
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NetatmoDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService, DiscoveryService {
|
||||
private static final Set<ModuleType> SKIPPED_TYPES = Set.of(ModuleType.UNKNOWN, ModuleType.ACCOUNT);
|
||||
private static final int DISCOVER_TIMEOUT_SECONDS = 5;
|
||||
private final Logger logger = LoggerFactory.getLogger(NetatmoDiscoveryService.class);
|
||||
private @Nullable ApiBridgeHandler handler;
|
||||
private @Nullable BindingConfiguration config;
|
||||
|
||||
public NetatmoDiscoveryService() {
|
||||
super(ModuleType.AS_SET.stream().filter(mt -> !SKIPPED_TYPES.contains(mt)).map(mt -> mt.thingTypeUID)
|
||||
.collect(Collectors.toSet()), DISCOVER_TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
BindingConfiguration localConf = config;
|
||||
ApiBridgeHandler localHandler = handler;
|
||||
if (localHandler != null && localConf != null) {
|
||||
ThingUID apiBridgeUID = localHandler.getThing().getUID();
|
||||
try {
|
||||
AircareApi airCareApi = localHandler.getRestManager(AircareApi.class);
|
||||
if (airCareApi != null) { // Search Healthy Home Coaches
|
||||
ListBodyResponse<NAMain> body = airCareApi.getHomeCoachData(null).getBody();
|
||||
if (body != null) {
|
||||
body.getElements().stream().forEach(homeCoach -> createThing(homeCoach, apiBridgeUID));
|
||||
}
|
||||
}
|
||||
if (localConf.readFriends) {
|
||||
WeatherApi weatherApi = localHandler.getRestManager(WeatherApi.class);
|
||||
if (weatherApi != null) { // Search favorite stations
|
||||
ListBodyResponse<NAMain> body = weatherApi.getStationsData(null, true).getBody();
|
||||
if (body != null) {
|
||||
body.getElements().stream().filter(NAMain::isReadOnly).forEach(station -> {
|
||||
ThingUID bridgeUID = createThing(station, apiBridgeUID);
|
||||
station.getModules().values().stream()
|
||||
.forEach(module -> createThing(module, bridgeUID));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
HomeApi homeApi = localHandler.getRestManager(HomeApi.class);
|
||||
if (homeApi != null) { // Search all the rest
|
||||
homeApi.getHomesData(null, null).stream().filter(h -> !h.getFeatures().isEmpty()).forEach(home -> {
|
||||
ThingUID homeUID = createThing(home, apiBridgeUID);
|
||||
home.getKnownPersons().forEach(person -> createThing(person, homeUID));
|
||||
home.getModules().values().stream().forEach(device -> {
|
||||
ModuleType deviceType = device.getType();
|
||||
String deviceBridge = device.getBridge();
|
||||
ThingUID bridgeUID = deviceBridge != null && deviceType.getBridge() != ModuleType.HOME
|
||||
? findThingUID(deviceType.getBridge(), deviceBridge, apiBridgeUID)
|
||||
: deviceType.getBridge() == ModuleType.HOME ? homeUID : apiBridgeUID;
|
||||
createThing(device, bridgeUID);
|
||||
});
|
||||
home.getRooms().values().stream().forEach(room -> {
|
||||
room.getModuleIds().stream().map(id -> home.getModules().get(id))
|
||||
.map(m -> m != null ? m.getType().feature : FeatureArea.NONE)
|
||||
.filter(f -> FeatureArea.ENERGY.equals(f)).findAny()
|
||||
.ifPresent(f -> createThing(room, homeUID));
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error during discovery process : {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ThingUID findThingUID(ModuleType thingType, String thingId, @Nullable ThingUID brigdeUID) {
|
||||
for (ThingTypeUID supported : getSupportedThingTypes()) {
|
||||
ThingTypeUID thingTypeUID = thingType.thingTypeUID;
|
||||
if (supported.equals(thingTypeUID)) {
|
||||
String id = thingId.replaceAll("[^a-zA-Z0-9_]", "");
|
||||
return brigdeUID == null ? new ThingUID(supported, id) : new ThingUID(supported, brigdeUID, id);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported device type discovered : " + thingType);
|
||||
}
|
||||
|
||||
private ThingUID createThing(NAModule module, @Nullable ThingUID bridgeUID) {
|
||||
ThingUID moduleUID = findThingUID(module.getType(), module.getId(), bridgeUID);
|
||||
DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(moduleUID)
|
||||
.withProperty(EQUIPMENT_ID, module.getId()).withRepresentationProperty(EQUIPMENT_ID)
|
||||
.withLabel(module.getName() != null ? module.getName() : module.getId());
|
||||
if (bridgeUID != null) {
|
||||
resultBuilder.withBridge(bridgeUID);
|
||||
}
|
||||
thingDiscovered(resultBuilder.build());
|
||||
return moduleUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
if (handler instanceof ApiBridgeHandler) {
|
||||
this.handler = (ApiBridgeHandler) handler;
|
||||
this.config = ((ApiBridgeHandler) handler).getConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
}
|
@ -1,253 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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<String, Object> 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<NAWelcomeCamera> 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<String, Object> 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<String, Object> 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;
|
||||
}
|
||||
}
|
@ -1,268 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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 static org.openhab.core.library.unit.MetricPrefix.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Angle;
|
||||
import javax.measure.quantity.Dimensionless;
|
||||
import javax.measure.quantity.Length;
|
||||
import javax.measure.quantity.Pressure;
|
||||
import javax.measure.quantity.Speed;
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.channelhelper.BatteryHelper;
|
||||
import org.openhab.binding.netatmo.internal.channelhelper.RadioHelper;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.thing.type.ChannelKind;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link AbstractNetatmoThingHandler} is the abstract class that handles
|
||||
* common behaviors of all netatmo things
|
||||
*
|
||||
* @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 abstract class AbstractNetatmoThingHandler extends BaseThingHandler {
|
||||
// Units of measurement of the data delivered by the API
|
||||
public static final Unit<Temperature> API_TEMPERATURE_UNIT = SIUnits.CELSIUS;
|
||||
public static final Unit<Dimensionless> API_HUMIDITY_UNIT = Units.PERCENT;
|
||||
public static final Unit<Pressure> API_PRESSURE_UNIT = HECTO(SIUnits.PASCAL);
|
||||
public static final Unit<Speed> API_WIND_SPEED_UNIT = SIUnits.KILOMETRE_PER_HOUR;
|
||||
public static final Unit<Angle> API_WIND_DIRECTION_UNIT = Units.DEGREE_ANGLE;
|
||||
public static final Unit<Length> API_RAIN_UNIT = MILLI(SIUnits.METRE);
|
||||
public static final Unit<Dimensionless> API_CO2_UNIT = Units.PARTS_PER_MILLION;
|
||||
public static final Unit<Dimensionless> API_NOISE_UNIT = Units.DECIBEL;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractNetatmoThingHandler.class);
|
||||
|
||||
protected final TimeZoneProvider timeZoneProvider;
|
||||
private @Nullable RadioHelper radioHelper;
|
||||
private @Nullable BatteryHelper batteryHelper;
|
||||
protected @Nullable Configuration config;
|
||||
private @Nullable NetatmoBridgeHandler bridgeHandler;
|
||||
|
||||
AbstractNetatmoThingHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing);
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("initializing handler for thing {}", getThing().getUID());
|
||||
Bridge bridge = getBridge();
|
||||
initializeThing(bridge != null ? bridge.getStatus() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("bridgeStatusChanged {} for thing {}", bridgeStatusInfo, getThing().getUID());
|
||||
initializeThing(bridgeStatusInfo.getStatus());
|
||||
}
|
||||
|
||||
private void initializeThing(@Nullable ThingStatus bridgeStatus) {
|
||||
Bridge bridge = getBridge();
|
||||
BridgeHandler bridgeHandler = bridge != null ? bridge.getHandler() : null;
|
||||
if (bridgeHandler != null && bridgeStatus != null) {
|
||||
if (bridgeStatus == ThingStatus.ONLINE) {
|
||||
config = getThing().getConfiguration();
|
||||
|
||||
String signalLevel = thing.getProperties().get(PROPERTY_SIGNAL_LEVELS);
|
||||
radioHelper = signalLevel != null ? new RadioHelper(signalLevel) : null;
|
||||
String batteryLevel = thing.getProperties().get(PROPERTY_BATTERY_LEVELS);
|
||||
batteryHelper = batteryLevel != null ? new BatteryHelper(batteryLevel) : null;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Pending parent object initialization");
|
||||
|
||||
initializeThing();
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void initializeThing();
|
||||
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
Optional<State> result = getBatteryHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
|
||||
if (result.isPresent()) {
|
||||
return result.get();
|
||||
}
|
||||
result = getRadioHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
|
||||
if (result.isPresent()) {
|
||||
return result.get();
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
protected void updateChannels() {
|
||||
if (thing.getStatus() != ThingStatus.ONLINE) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateDataChannels();
|
||||
|
||||
triggerEventChannels();
|
||||
}
|
||||
|
||||
private void updateDataChannels() {
|
||||
getThing().getChannels().stream()
|
||||
.filter(channel -> !ChannelKind.TRIGGER.equals(channel.getKind()) && isLinked(channel.getUID()))
|
||||
.map(channel -> channel.getUID()).forEach(this::updateChannel);
|
||||
}
|
||||
|
||||
private void updateChannel(ChannelUID channelUID) {
|
||||
updateState(channelUID, getNAThingProperty(channelUID.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers all event/trigger channels
|
||||
* (when a channel is triggered, a rule can get all other information from the updated non-trigger channels)
|
||||
*/
|
||||
private void triggerEventChannels() {
|
||||
getThing().getChannels().stream().filter(channel -> ChannelKind.TRIGGER.equals(channel.getKind()))
|
||||
.map(channel -> channel.getUID().getId()).forEach(this::triggerChannelIfRequired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the trigger channel with the given channel id when required (when an update is available)
|
||||
*
|
||||
* @param channelId channel id
|
||||
*/
|
||||
protected void triggerChannelIfRequired(String channelId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
logger.debug("Refreshing '{}'", channelUID);
|
||||
updateChannel(channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
protected Optional<NetatmoBridgeHandler> getBridgeHandler() {
|
||||
if (bridgeHandler == null) {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
bridgeHandler = (NetatmoBridgeHandler) bridge.getHandler();
|
||||
}
|
||||
}
|
||||
NetatmoBridgeHandler handler = bridgeHandler;
|
||||
return handler != null ? Optional.of(handler) : Optional.empty();
|
||||
}
|
||||
|
||||
protected Optional<AbstractNetatmoThingHandler> findNAThing(@Nullable String searchedId) {
|
||||
return getBridgeHandler().flatMap(handler -> handler.findNAThing(searchedId));
|
||||
}
|
||||
|
||||
public boolean matchesId(@Nullable String searchedId) {
|
||||
return searchedId != null && searchedId.equalsIgnoreCase(getId());
|
||||
}
|
||||
|
||||
protected @Nullable String getId() {
|
||||
Configuration conf = config;
|
||||
Object equipmentId = conf != null ? conf.get(EQUIPMENT_ID) : null;
|
||||
if (equipmentId instanceof String) {
|
||||
return ((String) equipmentId).toLowerCase();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void updateProperties(@Nullable Integer firmware, @Nullable String modelId) {
|
||||
Map<String, String> properties = editProperties();
|
||||
if (firmware != null || modelId != null) {
|
||||
properties.put(Thing.PROPERTY_VENDOR, VENDOR);
|
||||
}
|
||||
if (firmware != null) {
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmware.toString());
|
||||
}
|
||||
if (modelId != null) {
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, modelId);
|
||||
}
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
protected Optional<RadioHelper> getRadioHelper() {
|
||||
RadioHelper helper = radioHelper;
|
||||
return helper != null ? Optional.of(helper) : Optional.empty();
|
||||
}
|
||||
|
||||
protected Optional<BatteryHelper> getBatteryHelper() {
|
||||
BatteryHelper helper = batteryHelper;
|
||||
return helper != null ? Optional.of(helper) : Optional.empty();
|
||||
}
|
||||
|
||||
public void updateMeasurements() {
|
||||
}
|
||||
|
||||
public void getMeasurements(@Nullable String device, @Nullable String module, String scale, List<String> types,
|
||||
List<String> channels, Map<String, Float> channelMeasurements) {
|
||||
Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
|
||||
if (!handler.isPresent() || device == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (types.size() != channels.size()) {
|
||||
throw new IllegalArgumentException("types and channels lists are different sizes.");
|
||||
}
|
||||
|
||||
List<Float> measurements = handler.get().getStationMeasureResponses(device, module, scale, types);
|
||||
if (measurements.size() != types.size()) {
|
||||
throw new IllegalArgumentException("types and measurements lists are different sizes.");
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (Float measurement : measurements) {
|
||||
channelMeasurements.put(channels.get(i++), measurement);
|
||||
}
|
||||
}
|
||||
|
||||
public void addMeasurement(List<String> channels, List<String> types, String channel, String type) {
|
||||
if (isLinked(channel)) {
|
||||
channels.add(channel);
|
||||
types.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isReachable() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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 java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
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.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.InputStreamContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpStatus.Code;
|
||||
import org.openhab.binding.netatmo.internal.api.ApiError;
|
||||
import org.openhab.binding.netatmo.internal.api.AuthenticationApi;
|
||||
import org.openhab.binding.netatmo.internal.api.NetatmoException;
|
||||
import org.openhab.binding.netatmo.internal.api.RestManager;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
|
||||
import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration;
|
||||
import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration.Credentials;
|
||||
import org.openhab.binding.netatmo.internal.config.BindingConfiguration;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NADeserializer;
|
||||
import org.openhab.binding.netatmo.internal.discovery.NetatmoDiscoveryService;
|
||||
import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet;
|
||||
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.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link ApiBridgeHandler} is the handler for a Netatmo API and connects it to the framework.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ApiBridgeHandler extends BaseBridgeHandler {
|
||||
private static final int TIMEOUT_S = 20;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ApiBridgeHandler.class);
|
||||
private final BindingConfiguration bindingConf;
|
||||
private final HttpService httpService;
|
||||
private final AuthenticationApi connectApi;
|
||||
private final HttpClient httpClient;
|
||||
private final NADeserializer deserializer;
|
||||
|
||||
private Optional<ScheduledFuture<?>> connectJob = Optional.empty();
|
||||
private Optional<NetatmoServlet> servlet = Optional.empty();
|
||||
private @NonNullByDefault({}) ApiHandlerConfiguration thingConf;
|
||||
|
||||
private Map<Class<? extends RestManager>, RestManager> managers = new HashMap<>();
|
||||
|
||||
public ApiBridgeHandler(Bridge bridge, HttpClient httpClient, HttpService httpService, NADeserializer deserializer,
|
||||
BindingConfiguration configuration) {
|
||||
super(bridge);
|
||||
this.bindingConf = configuration;
|
||||
this.httpService = httpService;
|
||||
this.connectApi = new AuthenticationApi(this, scheduler);
|
||||
this.httpClient = httpClient;
|
||||
this.deserializer = deserializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing Netatmo API bridge handler.");
|
||||
thingConf = getConfigAs(ApiHandlerConfiguration.class);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
scheduler.execute(() -> {
|
||||
openConnection();
|
||||
String webHookUrl = thingConf.webHookUrl;
|
||||
if (webHookUrl != null && !webHookUrl.isBlank()) {
|
||||
servlet = Optional.of(new NetatmoServlet(httpService, this, webHookUrl));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openConnection() {
|
||||
try {
|
||||
Credentials credentials = thingConf.getCredentials();
|
||||
logger.debug("Connecting to Netatmo API.");
|
||||
try {
|
||||
connectApi.authenticate(credentials, bindingConf.features);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
getThing().getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler).filter(Objects::nonNull)
|
||||
.map(CommonInterface.class::cast).forEach(CommonInterface::expireData);
|
||||
} catch (NetatmoException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
prepareReconnection();
|
||||
}
|
||||
} catch (NetatmoException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareReconnection() {
|
||||
connectApi.disconnect();
|
||||
freeConnectJob();
|
||||
connectJob = Optional
|
||||
.of(scheduler.schedule(() -> openConnection(), thingConf.reconnectInterval, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
private void freeConnectJob() {
|
||||
connectJob.ifPresent(j -> j.cancel(true));
|
||||
connectJob = Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Shutting down Netatmo API bridge handler.");
|
||||
servlet.ifPresent(servlet -> servlet.dispose());
|
||||
servlet = Optional.empty();
|
||||
connectApi.dispose();
|
||||
freeConnectJob();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Netatmo Bridge is read-only and does not handle commands");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Set.of(NetatmoDiscoveryService.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends RestManager> @Nullable T getRestManager(Class<T> clazz) {
|
||||
if (!managers.containsKey(clazz)) {
|
||||
try {
|
||||
Constructor<T> constructor = clazz.getConstructor(getClass());
|
||||
T instance = constructor.newInstance(this);
|
||||
Set<Scope> expected = instance.getRequiredScopes();
|
||||
if (connectApi.matchesScopes(expected)) {
|
||||
managers.put(clazz, instance);
|
||||
} else {
|
||||
logger.info("Unable to instantiate {}, expected scope {} is not active", clazz, expected);
|
||||
}
|
||||
} catch (SecurityException | ReflectiveOperationException e) {
|
||||
logger.warn("Error invoking RestManager constructor for class {} : {}", clazz, e.getMessage());
|
||||
}
|
||||
}
|
||||
return (T) managers.get(clazz);
|
||||
}
|
||||
|
||||
public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nullable String payload,
|
||||
@Nullable String contentType, int retryCount) throws NetatmoException {
|
||||
try {
|
||||
logger.trace("executeUri {} {} ", method.toString(), uri);
|
||||
|
||||
Request request = httpClient.newRequest(uri).method(method).timeout(TIMEOUT_S, TimeUnit.SECONDS);
|
||||
|
||||
String auth = connectApi.getAuthorization();
|
||||
if (auth != null) {
|
||||
request.header(HttpHeader.AUTHORIZATION, auth);
|
||||
}
|
||||
|
||||
if (payload != null && contentType != null
|
||||
&& (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method))) {
|
||||
InputStream stream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
|
||||
try (InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider(stream)) {
|
||||
request.content(inputStreamContentProvider, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
ContentResponse response = request.send();
|
||||
|
||||
Code statusCode = HttpStatus.getCode(response.getStatus());
|
||||
String responseBody = new String(response.getContent(), StandardCharsets.UTF_8);
|
||||
logger.trace("executeUri returned : code {} body {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != Code.OK) {
|
||||
ApiError error = deserializer.deserialize(ApiError.class, responseBody);
|
||||
throw new NetatmoException(error);
|
||||
}
|
||||
return deserializer.deserialize(clazz, responseBody);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
if (retryCount > 0) {
|
||||
logger.debug("Request timedout, retry counter : {}", retryCount);
|
||||
return executeUri(uri, method, clazz, payload, contentType, retryCount - 1);
|
||||
}
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/request-time-out");
|
||||
prepareReconnection();
|
||||
throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public BindingConfiguration getConfiguration() {
|
||||
return bindingConf;
|
||||
}
|
||||
|
||||
public Optional<NetatmoServlet> getServlet() {
|
||||
return servlet;
|
||||
}
|
||||
|
||||
public NADeserializer getDeserializer() {
|
||||
return deserializer;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connectApi.isConnected();
|
||||
}
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.config.NAThingConfiguration;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.Capability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.RestCapability;
|
||||
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.BridgeHandler;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelKind;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* {@link CommonInterface} defines common methods of AccountHandler and NAThingHandlers used by Capabilities
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface CommonInterface {
|
||||
Thing getThing();
|
||||
|
||||
ThingBuilder editThing();
|
||||
|
||||
CapabilityMap getCapabilities();
|
||||
|
||||
Logger getLogger();
|
||||
|
||||
ScheduledExecutorService getScheduler();
|
||||
|
||||
boolean isLinked(ChannelUID channelUID);
|
||||
|
||||
void updateState(ChannelUID channelUID, State state);
|
||||
|
||||
void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail,
|
||||
@Nullable String thingStatusReason);
|
||||
|
||||
void triggerChannel(String channelID, String event);
|
||||
|
||||
void updateThing(Thing thing);
|
||||
|
||||
@Nullable
|
||||
Bridge getBridge();
|
||||
|
||||
default @Nullable CommonInterface getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
return bridge != null && bridge.getHandler() instanceof DeviceHandler ? (DeviceHandler) bridge.getHandler()
|
||||
: null;
|
||||
}
|
||||
|
||||
default @Nullable ApiBridgeHandler getAccountHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
BridgeHandler bridgeHandler = null;
|
||||
if (bridge != null) {
|
||||
bridgeHandler = bridge.getHandler();
|
||||
while (bridgeHandler != null && !(bridgeHandler instanceof ApiBridgeHandler)) {
|
||||
bridge = ((CommonInterface) bridgeHandler).getBridge();
|
||||
bridgeHandler = bridge != null ? bridge.getHandler() : null;
|
||||
}
|
||||
}
|
||||
return (ApiBridgeHandler) bridgeHandler;
|
||||
}
|
||||
|
||||
default @Nullable String getBridgeId() {
|
||||
CommonInterface bridge = getBridgeHandler();
|
||||
return bridge != null ? bridge.getId() : null;
|
||||
}
|
||||
|
||||
default void expireData() {
|
||||
getCapabilities().values().forEach(cap -> cap.expireData());
|
||||
}
|
||||
|
||||
default String getId() {
|
||||
return (String) getThing().getConfiguration().get("id");
|
||||
}
|
||||
|
||||
default Stream<Channel> getActiveChannels() {
|
||||
return getThing().getChannels().stream()
|
||||
.filter(channel -> ChannelKind.STATE.equals(channel.getKind()) && isLinked(channel.getUID()));
|
||||
}
|
||||
|
||||
default Optional<CommonInterface> getHomeHandler() {
|
||||
CommonInterface bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
return bridgeHandler.getCapabilities().get(HomeCapability.class).isPresent() ? Optional.of(bridgeHandler)
|
||||
: Optional.empty();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default List<CommonInterface> getActiveChildren() {
|
||||
Thing thing = getThing();
|
||||
if (thing instanceof Bridge) {
|
||||
return ((Bridge) thing).getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler)
|
||||
.filter(Objects::nonNull).map(CommonInterface.class::cast).collect(Collectors.toList());
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
default <T extends RestCapability<?>> Optional<T> getHomeCapability(Class<T> clazz) {
|
||||
return getHomeHandler().map(handler -> handler.getCapabilities().get(clazz)).orElse(Optional.empty());
|
||||
}
|
||||
|
||||
default void setNewData(NAObject newData) {
|
||||
String finalReason = null;
|
||||
for (Capability cap : getCapabilities().values()) {
|
||||
String thingStatusReason = cap.setNewData(newData);
|
||||
if (thingStatusReason != null) {
|
||||
finalReason = thingStatusReason;
|
||||
}
|
||||
}
|
||||
if (!newData.isIgnoredForThingUpdate()) {
|
||||
setThingStatus(finalReason == null ? ThingStatus.ONLINE : ThingStatus.OFFLINE, ThingStatusDetail.NONE,
|
||||
finalReason);
|
||||
}
|
||||
}
|
||||
|
||||
default void commonHandleCommand(ChannelUID channelUID, Command command) {
|
||||
if (ThingStatus.ONLINE.equals(getThing().getStatus())) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
expireData();
|
||||
return;
|
||||
}
|
||||
String channelName = channelUID.getIdWithoutGroup();
|
||||
getCapabilities().values().forEach(cap -> cap.handleCommand(channelName, command));
|
||||
} else {
|
||||
getLogger().debug("Command {}, on channel {} dropped - thing is not ONLINE", command, channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
default void proceedWithUpdate() {
|
||||
updateReadings().forEach(dataSet -> setNewData(dataSet));
|
||||
}
|
||||
|
||||
default List<NAObject> updateReadings() {
|
||||
List<NAObject> result = new ArrayList<>();
|
||||
getCapabilities().values().forEach(cap -> result.addAll(cap.updateReadings()));
|
||||
getActiveChildren().forEach(child -> result.addAll(child.updateReadings()));
|
||||
return result;
|
||||
}
|
||||
|
||||
default void commonInitialize() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null || bridge.getHandler() == null) {
|
||||
setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, null);
|
||||
} else if (!ThingStatus.ONLINE.equals(bridge.getStatus())) {
|
||||
setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
|
||||
removeRefreshCapability();
|
||||
} else {
|
||||
setThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null);
|
||||
setRefreshCapability();
|
||||
getCapabilities().values().forEach(cap -> cap.initialize());
|
||||
getScheduler().schedule(() -> {
|
||||
CommonInterface bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.expireData();
|
||||
}
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
default void setRefreshCapability() {
|
||||
ModuleType moduleType = ModuleType.from(getThing().getThingTypeUID());
|
||||
if (ModuleType.ACCOUNT.equals(moduleType.getBridge())) {
|
||||
NAThingConfiguration config = getThing().getConfiguration().as(NAThingConfiguration.class);
|
||||
getCapabilities().put(new RefreshCapability(this, getScheduler(), config.refreshInterval));
|
||||
}
|
||||
}
|
||||
|
||||
default void removeRefreshCapability() {
|
||||
Capability refreshCap = getCapabilities().remove(RefreshCapability.class);
|
||||
if (refreshCap != null) {
|
||||
refreshCap.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
default void commonDispose() {
|
||||
getCapabilities().values().forEach(Capability::dispose);
|
||||
}
|
||||
|
||||
default void removeChannels(List<Channel> channels) {
|
||||
ThingBuilder builder = editThing().withoutChannels(channels);
|
||||
updateThing(builder.build());
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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 java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.builder.BridgeBuilder;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link DeviceHandler} is the base class for all Netatmo bridges
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceHandler extends BaseBridgeHandler implements CommonInterface {
|
||||
private final Logger logger = LoggerFactory.getLogger(DeviceHandler.class);
|
||||
private CapabilityMap capabilities = new CapabilityMap();
|
||||
|
||||
public DeviceHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing handler for bridge {}", getThing().getUID());
|
||||
commonInitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("bridgeStatusChanged for bridge {} to {}", getThing().getUID(), bridgeStatusInfo);
|
||||
commonInitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
commonDispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
commonHandleCommand(channelUID, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail,
|
||||
@Nullable String thingStatusReason) {
|
||||
updateStatus(thingStatus, thingStatusDetail, thingStatusReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CapabilityMap getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BridgeBuilder editThing() {
|
||||
return super.editThing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThing(Thing thing) {
|
||||
super.updateThing(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(ChannelUID channelUID, State state) {
|
||||
super.updateState(channelUID, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLinked(ChannelUID channelUID) {
|
||||
return super.isLinked(channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Bridge getBridge() {
|
||||
return super.getBridge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerChannel(String channelID, String event) {
|
||||
super.triggerChannel(channelID, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledExecutorService getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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 java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link ModuleHandler} is the base class for all Netatmo things
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModuleHandler extends BaseThingHandler implements CommonInterface {
|
||||
private final Logger logger = LoggerFactory.getLogger(ModuleHandler.class);
|
||||
private CapabilityMap capabilities = new CapabilityMap();
|
||||
|
||||
public ModuleHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing handler for thing {}", getThing().getUID());
|
||||
commonInitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("bridgeStatusChanged for thing {} to {}", getThing().getUID(), bridgeStatusInfo);
|
||||
commonInitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
commonDispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
commonHandleCommand(channelUID, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail,
|
||||
@Nullable String thingStatusReason) {
|
||||
updateStatus(thingStatus, thingStatusDetail, thingStatusReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CapabilityMap getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingBuilder editThing() {
|
||||
return super.editThing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThing(Thing thing) {
|
||||
super.updateThing(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(ChannelUID channelUID, State state) {
|
||||
super.updateState(channelUID, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLinked(ChannelUID channelUID) {
|
||||
return super.isLinked(channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Bridge getBridge() {
|
||||
return super.getBridge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerChannel(String channelID, String event) {
|
||||
super.triggerChannel(channelID, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
List<Class<? extends ThingHandlerService>> result = new ArrayList<>();
|
||||
capabilities.values().forEach(cap -> result.addAll(cap.getServices()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledExecutorService getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
}
|
@ -1,408 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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<NetatmoDataListener> dataListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
private static class APICreator {
|
||||
|
||||
private final ApiClient apiClient;
|
||||
private final Map<Class<?>, Object> apiMap;
|
||||
|
||||
private APICreator(ApiClient apiClient) {
|
||||
super();
|
||||
this.apiClient = apiClient;
|
||||
apiMap = new HashMap<>();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getAPI(Class<T> 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<String> 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<StationApi> getStationApi() {
|
||||
return apiCreator != null ? Optional.of(apiCreator.getAPI(StationApi.class)) : Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<HealthyhomecoachApi> getHomeCoachApi() {
|
||||
return apiCreator != null ? Optional.of(apiCreator.getAPI(HealthyhomecoachApi.class)) : Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ThermostatApi> getThermostatApi() {
|
||||
return apiCreator != null ? Optional.of(apiCreator.getAPI(ThermostatApi.class)) : Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<WelcomeApi> 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<NAStationDataBody> getStationsDataBody(@Nullable String equipmentId) {
|
||||
Optional<NAStationDataBody> data = getStationApi().map(api -> api.getstationsdata(equipmentId, true).getBody());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
return data;
|
||||
}
|
||||
|
||||
public List<Float> getStationMeasureResponses(String equipmentId, @Nullable String moduleId, String scale,
|
||||
List<String> types) {
|
||||
List<NAMeasureBodyElem> 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<NAHealthyHomeCoachDataBody> getHomecoachDataBody(@Nullable String equipmentId) {
|
||||
Optional<NAHealthyHomeCoachDataBody> data = getHomeCoachApi()
|
||||
.map(api -> api.gethomecoachsdata(equipmentId).getBody());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
return data;
|
||||
}
|
||||
|
||||
public Optional<NAThermostatDataBody> getThermostatsDataBody(@Nullable String equipmentId) {
|
||||
Optional<NAThermostatDataBody> data = getThermostatApi()
|
||||
.map(api -> api.getthermostatsdata(equipmentId).getBody());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
return data;
|
||||
}
|
||||
|
||||
public Optional<NAWelcomeHomeData> getWelcomeDataBody(@Nullable String homeId) {
|
||||
Optional<NAWelcomeHomeData> 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<AbstractNetatmoThingHandler> findNAThing(@Nullable String searchedId) {
|
||||
List<Thing> things = getThing().getThings();
|
||||
Stream<AbstractNetatmoThingHandler> 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<AbstractNetatmoThingHandler> modules = new HashSet<>();
|
||||
if (WELCOME_EVENTS.contains(event.getEventType()) || PRESENCE_EVENTS.contains(event.getEventType())) {
|
||||
String cameraId = event.getCameraId();
|
||||
if (cameraId != null) {
|
||||
Optional<AbstractNetatmoThingHandler> camera = findNAThing(cameraId);
|
||||
camera.ifPresent(modules::add);
|
||||
}
|
||||
}
|
||||
if (HOME_EVENTS.contains(event.getEventType())) {
|
||||
String homeId = event.getHomeId();
|
||||
if (homeId != null) {
|
||||
Optional<AbstractNetatmoThingHandler> home = findNAThing(homeId);
|
||||
home.ifPresent(modules::add);
|
||||
}
|
||||
}
|
||||
if (PERSON_EVENTS.contains(event.getEventType())) {
|
||||
List<NAWebhookCameraEventPerson> persons = event.getPersons();
|
||||
persons.forEach(person -> {
|
||||
String personId = person.getId();
|
||||
if (personId != null) {
|
||||
Optional<AbstractNetatmoThingHandler> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link NetatmoDataListener} allows receiving notification when any netatmo device thing handler
|
||||
* is getting refreshed data from the netatmo server.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface NetatmoDataListener {
|
||||
|
||||
/**
|
||||
* This method is called just after the thing handler fetched new data from the netatmo server.
|
||||
*
|
||||
* @param data the retrieved data.
|
||||
*/
|
||||
public void onDataRefreshed(Object data);
|
||||
}
|
@ -1,257 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
|
||||
import org.openhab.binding.netatmo.internal.RefreshStrategy;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.swagger.client.ApiException;
|
||||
import io.swagger.client.model.NAPlace;
|
||||
|
||||
/**
|
||||
* {@link NetatmoDeviceHandler} is the handler for a given
|
||||
* device accessed through the Netatmo Bridge
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class NetatmoDeviceHandler<DEVICE> extends AbstractNetatmoThingHandler {
|
||||
|
||||
private static final int MIN_REFRESH_INTERVAL = 2000;
|
||||
private static final int DEFAULT_REFRESH_INTERVAL = 300000;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NetatmoDeviceHandler.class);
|
||||
private final Object updateLock = new Object();
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private @Nullable RefreshStrategy refreshStrategy;
|
||||
private @Nullable DEVICE device;
|
||||
protected Map<String, Object> childs = new ConcurrentHashMap<>();
|
||||
|
||||
public NetatmoDeviceHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeThing() {
|
||||
defineRefreshInterval();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
scheduleRefreshJob();
|
||||
}
|
||||
|
||||
private void scheduleRefreshJob() {
|
||||
RefreshStrategy strategy = refreshStrategy;
|
||||
if (strategy == null) {
|
||||
return;
|
||||
}
|
||||
long delay = strategy.nextRunDelayInS();
|
||||
logger.debug("Scheduling update channel thread in {} s", delay);
|
||||
refreshJob = scheduler.schedule(() -> {
|
||||
updateChannels(false);
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job != null) {
|
||||
job.cancel(false);
|
||||
refreshJob = null;
|
||||
}
|
||||
scheduleRefreshJob();
|
||||
}, delay, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Running dispose()");
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
refreshJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Optional<DEVICE> updateReadings();
|
||||
|
||||
protected void updateProperties(DEVICE deviceData) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateChannels() {
|
||||
updateChannels(true);
|
||||
}
|
||||
|
||||
private void updateChannels(boolean requireDefinedRefreshInterval) {
|
||||
// Avoid concurrent data readings
|
||||
synchronized (updateLock) {
|
||||
RefreshStrategy strategy = refreshStrategy;
|
||||
if (strategy != null) {
|
||||
logger.debug("Data aged of {} s", strategy.dataAge() / 1000);
|
||||
boolean dataOutdated = (requireDefinedRefreshInterval && strategy.isSearchingRefreshInterval()) ? false
|
||||
: strategy.isDataOutdated();
|
||||
if (dataOutdated) {
|
||||
logger.debug("Trying to update channels on device {}", getId());
|
||||
childs.clear();
|
||||
|
||||
Optional<DEVICE> newDeviceReading = Optional.empty();
|
||||
try {
|
||||
newDeviceReading = updateReadings();
|
||||
} catch (ApiException e) {
|
||||
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.COMMUNICATION_ERROR,
|
||||
"Unable to connect Netatmo API : " + e.getLocalizedMessage());
|
||||
}
|
||||
if (newDeviceReading.isPresent()) {
|
||||
logger.debug("Successfully updated device {} readings! Now updating channels", getId());
|
||||
DEVICE theDevice = newDeviceReading.get();
|
||||
this.device = theDevice;
|
||||
updateStatus(isReachable() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
|
||||
updateProperties(theDevice);
|
||||
getDataTimestamp().ifPresent(dataTimeStamp -> {
|
||||
strategy.setDataTimeStamp(dataTimeStamp, timeZoneProvider.getTimeZone());
|
||||
});
|
||||
getRadioHelper().ifPresent(helper -> helper.setModule(theDevice));
|
||||
getBridgeHandler().ifPresent(handler -> {
|
||||
handler.checkForNewThings(theDevice);
|
||||
});
|
||||
} else {
|
||||
logger.debug("Failed to update device {} readings! Skip updating channels", getId());
|
||||
}
|
||||
// Be sure that all channels for the modules will be updated with refreshed data
|
||||
childs.forEach((childId, moduleData) -> {
|
||||
findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> {
|
||||
naChildModule.setRefreshRequired(true);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
logger.debug("Data still valid for device {}", getId());
|
||||
}
|
||||
super.updateChannels();
|
||||
updateChildModules();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
try {
|
||||
Optional<DEVICE> dev = getDevice();
|
||||
switch (channelId) {
|
||||
case CHANNEL_LAST_STATUS_STORE:
|
||||
if (dev.isPresent()) {
|
||||
Method getLastStatusStore = dev.get().getClass().getMethod("getLastStatusStore");
|
||||
Integer lastStatusStore = (Integer) getLastStatusStore.invoke(dev.get());
|
||||
return ChannelTypeUtils.toDateTimeType(lastStatusStore, timeZoneProvider.getTimeZone());
|
||||
} else {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
case CHANNEL_LOCATION:
|
||||
if (dev.isPresent()) {
|
||||
Method getPlace = dev.get().getClass().getMethod("getPlace");
|
||||
NAPlace place = (NAPlace) getPlace.invoke(dev.get());
|
||||
PointType point = new PointType(new DecimalType(place.getLocation().get(1)),
|
||||
new DecimalType(place.getLocation().get(0)));
|
||||
if (place.getAltitude() != null) {
|
||||
point.setAltitude(new DecimalType(place.getAltitude()));
|
||||
}
|
||||
return point;
|
||||
} else {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
logger.debug("The device has no method to access {} property ", channelId);
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
private void updateChildModules() {
|
||||
logger.debug("Updating child modules of {}", getId());
|
||||
childs.forEach((childId, moduleData) -> {
|
||||
findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> {
|
||||
logger.debug("Updating child module {}", naChildModule.getId());
|
||||
naChildModule.updateChannels(moduleData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the refresh rate of the device depending whether it's a property
|
||||
* of the thing or if it's defined by configuration
|
||||
*/
|
||||
private void defineRefreshInterval() {
|
||||
BigDecimal dataValidityPeriod;
|
||||
if (thing.getProperties().containsKey(PROPERTY_REFRESH_PERIOD)) {
|
||||
String refreshPeriodProperty = thing.getProperties().get(PROPERTY_REFRESH_PERIOD);
|
||||
if ("auto".equalsIgnoreCase(refreshPeriodProperty)) {
|
||||
dataValidityPeriod = new BigDecimal(-1);
|
||||
} else {
|
||||
dataValidityPeriod = new BigDecimal(refreshPeriodProperty);
|
||||
}
|
||||
} else {
|
||||
Configuration conf = config;
|
||||
Object interval = conf != null ? conf.get(REFRESH_INTERVAL) : null;
|
||||
if (interval instanceof BigDecimal) {
|
||||
dataValidityPeriod = (BigDecimal) interval;
|
||||
if (dataValidityPeriod.intValue() < MIN_REFRESH_INTERVAL) {
|
||||
logger.info(
|
||||
"Refresh interval setting is too small for thing {}, {} ms is considered as refresh interval.",
|
||||
thing.getUID(), MIN_REFRESH_INTERVAL);
|
||||
dataValidityPeriod = new BigDecimal(MIN_REFRESH_INTERVAL);
|
||||
}
|
||||
} else {
|
||||
dataValidityPeriod = new BigDecimal(DEFAULT_REFRESH_INTERVAL);
|
||||
}
|
||||
}
|
||||
refreshStrategy = new RefreshStrategy(dataValidityPeriod.intValue());
|
||||
}
|
||||
|
||||
protected abstract Optional<Integer> getDataTimestamp();
|
||||
|
||||
public void expireData() {
|
||||
RefreshStrategy strategy = refreshStrategy;
|
||||
if (strategy != null) {
|
||||
strategy.expireData();
|
||||
}
|
||||
}
|
||||
|
||||
protected Optional<DEVICE> getDevice() {
|
||||
return Optional.ofNullable(device);
|
||||
}
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link NetatmoModuleHandler} is the handler for a given
|
||||
* module device accessed through the Netatmo Device
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NetatmoModuleHandler<MODULE> extends AbstractNetatmoThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(NetatmoModuleHandler.class);
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private @Nullable MODULE module;
|
||||
private boolean refreshRequired;
|
||||
|
||||
protected NetatmoModuleHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeThing() {
|
||||
refreshJob = scheduler.schedule(() -> {
|
||||
requestParentRefresh();
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
refreshJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected @Nullable String getParentId() {
|
||||
Configuration conf = config;
|
||||
Object parentId = conf != null ? conf.get(PARENT_ID) : null;
|
||||
if (parentId instanceof String) {
|
||||
return ((String) parentId).toLowerCase();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean childOf(AbstractNetatmoThingHandler naThingHandler) {
|
||||
return naThingHandler.matchesId(getParentId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
try {
|
||||
Optional<MODULE> mod = getModule();
|
||||
if (channelId.equalsIgnoreCase(CHANNEL_LAST_MESSAGE) && mod.isPresent()) {
|
||||
Method getLastMessage = mod.get().getClass().getMethod("getLastMessage");
|
||||
Integer lastMessage = (Integer) getLastMessage.invoke(mod.get());
|
||||
return ChannelTypeUtils.toDateTimeType(lastMessage, timeZoneProvider.getTimeZone());
|
||||
}
|
||||
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException e) {
|
||||
logger.debug("The module has no method to access {} property ", channelId);
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
protected void updateChannels(Object module) {
|
||||
MODULE theModule = (MODULE) module;
|
||||
setModule(theModule);
|
||||
updateStatus(isReachable() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
|
||||
getRadioHelper().ifPresent(helper -> helper.setModule(module));
|
||||
getBatteryHelper().ifPresent(helper -> helper.setModule(module));
|
||||
updateProperties(theModule);
|
||||
super.updateChannels();
|
||||
}
|
||||
|
||||
protected void invalidateParentCacheAndRefresh() {
|
||||
setRefreshRequired(true);
|
||||
// Leave a bit of time to Netatmo Server to get in sync with new values sent
|
||||
scheduler.schedule(() -> {
|
||||
invalidateParentCache();
|
||||
requestParentRefresh();
|
||||
}, 2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
protected void requestParentRefresh() {
|
||||
setRefreshRequired(true);
|
||||
findNAThing(getParentId()).ifPresent(AbstractNetatmoThingHandler::updateChannels);
|
||||
}
|
||||
|
||||
private void invalidateParentCache() {
|
||||
findNAThing(getParentId()).map(NetatmoDeviceHandler.class::cast).ifPresent(NetatmoDeviceHandler::expireData);
|
||||
}
|
||||
|
||||
protected void updateProperties(MODULE moduleData) {
|
||||
}
|
||||
|
||||
protected boolean isRefreshRequired() {
|
||||
return refreshRequired;
|
||||
}
|
||||
|
||||
protected void setRefreshRequired(boolean refreshRequired) {
|
||||
this.refreshRequired = refreshRequired;
|
||||
}
|
||||
|
||||
protected Optional<MODULE> getModule() {
|
||||
return Optional.ofNullable(module);
|
||||
}
|
||||
|
||||
public void setModule(MODULE module) {
|
||||
this.module = module;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.AircareApi;
|
||||
import org.openhab.binding.netatmo.internal.api.NetatmoException;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link AirCareCapability} give the ability to read home coach api
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirCareCapability extends RestCapability<AircareApi> {
|
||||
private final Logger logger = LoggerFactory.getLogger(AirCareCapability.class);
|
||||
|
||||
public AirCareCapability(CommonInterface handler) {
|
||||
super(handler, AircareApi.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<NAObject> updateReadings(AircareApi api) {
|
||||
try {
|
||||
return List.of(api.getHomeCoach(handler.getId()));
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error retrieving home-coach data '{}' : {}", handler.getId(), e.getMessage());
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.AlimentationStatus;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SdCardStatus;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.StateOption;
|
||||
|
||||
/**
|
||||
* {@link CameraCapability} give to handle Welcome Camera specifics
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CameraCapability extends HomeSecurityThingCapability {
|
||||
private final CameraChannelHelper cameraHelper;
|
||||
private final ChannelUID personChannelUID;
|
||||
|
||||
protected @Nullable String localUrl;
|
||||
|
||||
public CameraCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
|
||||
List<ChannelHelper> channelHelpers) {
|
||||
super(handler, descriptionProvider, channelHelpers);
|
||||
this.personChannelUID = new ChannelUID(thing.getUID(), GROUP_LAST_EVENT, CHANNEL_EVENT_PERSON_ID);
|
||||
this.cameraHelper = (CameraChannelHelper) channelHelpers.stream().filter(c -> c instanceof CameraChannelHelper)
|
||||
.findFirst().orElseThrow(() -> new IllegalArgumentException(
|
||||
"CameraCapability must find a CameraChannelHelper, please file a bug report."));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHomeStatusModule(HomeStatusModule newData) {
|
||||
super.updateHomeStatusModule(newData);
|
||||
String vpnUrl = newData.getVpnUrl();
|
||||
if (vpnUrl != null) {
|
||||
localUrl = newData.isLocal() ? securityCapability.map(cap -> cap.ping(vpnUrl)).orElse(null) : null;
|
||||
cameraHelper.setUrls(vpnUrl, localUrl);
|
||||
eventHelper.setUrls(vpnUrl, localUrl);
|
||||
}
|
||||
if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus())
|
||||
|| !AlimentationStatus.ALIM_CORRECT_POWER.equals(newData.getAlimStatus())) {
|
||||
statusReason = String.format("%s, %s", newData.getSdStatus(), newData.getAlimStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(String channelName, Command command) {
|
||||
if (command instanceof OnOffType && CHANNEL_MONITORING.equals(channelName)) {
|
||||
securityCapability.ifPresent(cap -> cap.changeStatus(localUrl, OnOffType.ON.equals(command)));
|
||||
} else {
|
||||
super.handleCommand(channelName, command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeNewData() {
|
||||
super.beforeNewData();
|
||||
homeCapability.ifPresent(cap -> {
|
||||
NAObjectMap<HomeDataPerson> persons = cap.getPersons();
|
||||
descriptionProvider.setStateOptions(personChannelUID, persons.values().stream()
|
||||
.map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NAObject> updateReadings() {
|
||||
List<NAObject> result = new ArrayList<>();
|
||||
securityCapability.ifPresent(cap -> {
|
||||
Collection<HomeEvent> events = cap.getCameraEvents(handler.getId());
|
||||
if (!events.isEmpty()) {
|
||||
result.add(events.iterator().next());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.VENDOR;
|
||||
import static org.openhab.core.thing.Thing.*;
|
||||
|
||||
import java.util.Collection;
|
||||
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.api.data.ModuleType;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.Device;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.Event;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeData;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAMain;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAThing;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The {@link Capability} is the base class for all inherited capabilities
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Capability {
|
||||
|
||||
protected final Thing thing;
|
||||
protected final CommonInterface handler;
|
||||
protected final ModuleType moduleType;
|
||||
|
||||
protected boolean firstLaunch;
|
||||
protected Map<String, String> properties = Map.of();
|
||||
protected @Nullable String statusReason;
|
||||
|
||||
Capability(CommonInterface handler) {
|
||||
this.handler = handler;
|
||||
this.thing = handler.getThing();
|
||||
this.moduleType = ModuleType.from(thing.getThingTypeUID());
|
||||
}
|
||||
|
||||
public final @Nullable String setNewData(NAObject newData) {
|
||||
beforeNewData();
|
||||
if (newData instanceof HomeData) {
|
||||
updateHomeData((HomeData) newData);
|
||||
}
|
||||
if (newData instanceof HomeStatus) {
|
||||
updateHomeStatus((HomeStatus) newData);
|
||||
}
|
||||
if (newData instanceof HomeStatusModule) {
|
||||
updateHomeStatusModule((HomeStatusModule) newData);
|
||||
}
|
||||
if (newData instanceof Event) {
|
||||
updateEvent((Event) newData);
|
||||
}
|
||||
if (newData instanceof HomeEvent) {
|
||||
updateHomeEvent((HomeEvent) newData);
|
||||
}
|
||||
if (newData instanceof NAThing) {
|
||||
updateNAThing((NAThing) newData);
|
||||
}
|
||||
if (newData instanceof NAMain) {
|
||||
updateNAMain((NAMain) newData);
|
||||
}
|
||||
if (newData instanceof Device) {
|
||||
updateNADevice((Device) newData);
|
||||
}
|
||||
afterNewData(newData);
|
||||
return statusReason;
|
||||
}
|
||||
|
||||
protected void beforeNewData() {
|
||||
properties = new HashMap<>(thing.getProperties());
|
||||
firstLaunch = properties.isEmpty();
|
||||
if (firstLaunch && !moduleType.isLogical()) {
|
||||
properties.put(PROPERTY_VENDOR, VENDOR);
|
||||
properties.put(PROPERTY_MODEL_ID, moduleType.name());
|
||||
}
|
||||
statusReason = null;
|
||||
}
|
||||
|
||||
protected void afterNewData(@Nullable NAObject newData) {
|
||||
if (!properties.equals(thing.getProperties())) {
|
||||
thing.setProperties(properties);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateNAThing(NAThing newData) {
|
||||
String firmware = newData.getFirmware();
|
||||
if (firmware != null && !firmware.isBlank()) {
|
||||
properties.put(PROPERTY_FIRMWARE_VERSION, firmware);
|
||||
}
|
||||
if (!newData.isReachable()) {
|
||||
statusReason = "@text/device-not-connected";
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateNAMain(NAMain newData) {
|
||||
// do nothing by default, can be overridden by subclasses
|
||||
}
|
||||
|
||||
protected void updateHomeEvent(HomeEvent newData) {
|
||||
// do nothing by default, can be overridden by subclasses
|
||||
}
|
||||
|
||||
protected void updateHomeStatus(HomeStatus newData) {
|
||||
// do nothing by default, can be overridden by subclasses
|
||||
}
|
||||
|
||||
protected void updateHomeData(HomeData newData) {
|
||||
// do nothing by default, can be overridden by subclasses
|
||||
}
|
||||
|
||||
protected void updateEvent(Event newData) {
|
||||
// do nothing by default, can be overridden by subclasses
|
||||
}
|
||||
|
||||
protected void updateNADevice(Device newData) {
|
||||
// do nothing by default, can be overridden by subclasses
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
// do nothing by default, can be overridden by subclasses
|
||||
}
|
||||
|
||||
public void expireData() {
|
||||
if (!handler.getCapabilities().containsKey(RefreshCapability.class)) {
|
||||
CommonInterface bridgeHandler = handler.getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.expireData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
// do nothing by default, can be overridden by subclasses
|
||||
}
|
||||
|
||||
public void updateHomeStatusModule(HomeStatusModule newData) {
|
||||
// do nothing by default, can be overridden by subclasses
|
||||
}
|
||||
|
||||
public void handleCommand(String channelName, Command command) {
|
||||
// do nothing by default, can be overridden by subclasses
|
||||
}
|
||||
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
public List<NAObject> updateReadings() {
|
||||
return List.of();
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link CapabilityMap} is a specialized Map designed to store capabilities
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CapabilityMap extends ConcurrentHashMap<Class<?>, Capability> {
|
||||
private static final long serialVersionUID = -3043492242108419801L;
|
||||
|
||||
public void put(Capability capability) {
|
||||
Class<? extends Capability> clazz = capability.getClass();
|
||||
if (super.get(clazz) == null) {
|
||||
super.put(clazz, capability);
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends Capability> Optional<T> get(Class<T> clazz) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T cap = (T) super.get(clazz);
|
||||
return Optional.ofNullable(cap);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* {@link ChannelHelperCapability} give the capability to dispatch incoming data across the channel helpers.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChannelHelperCapability extends Capability {
|
||||
private final List<ChannelHelper> channelHelpers;
|
||||
|
||||
public ChannelHelperCapability(CommonInterface handler, List<ChannelHelper> channelHelpers) {
|
||||
super(handler);
|
||||
this.channelHelpers = channelHelpers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterNewData(@Nullable NAObject newData) {
|
||||
super.afterNewData(newData);
|
||||
channelHelpers.forEach(helper -> helper.setNewData(newData));
|
||||
handler.getActiveChannels().forEach(channel -> {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
String channelID = channelUID.getIdWithoutGroup();
|
||||
String groupId = channelUID.getGroupId();
|
||||
Configuration channelConfig = channel.getConfiguration();
|
||||
for (ChannelHelper helper : channelHelpers) {
|
||||
State state = helper.getChannelState(channelID, groupId, channelConfig);
|
||||
if (state != null) {
|
||||
handler.updateState(channelUID, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAMain;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
|
||||
/**
|
||||
* The {@link DeviceCapability} takes care of handling properties for netatmo devices
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceCapability extends Capability {
|
||||
private static final int DATA_AGE_LIMIT_S = 3600;
|
||||
|
||||
public DeviceCapability(CommonInterface handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateNAMain(NAMain newData) {
|
||||
if (firstLaunch) {
|
||||
newData.getPlace().ifPresent(place -> {
|
||||
place.getCity().map(city -> properties.put(PROPERTY_CITY, city));
|
||||
place.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country));
|
||||
place.getTimezone().map(tz -> properties.put(PROPERTY_TIMEZONE, tz));
|
||||
});
|
||||
}
|
||||
if (!newData.hasFreshData(DATA_AGE_LIMIT_S)) {
|
||||
statusReason = "@text/data-over-limit";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.EnergyApi;
|
||||
import org.openhab.binding.netatmo.internal.api.NetatmoException;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeData;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeDataRoom;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.Room;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link EnergyCapability} is the base class for handler able to handle energy features
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EnergyCapability extends RestCapability<EnergyApi> {
|
||||
private final Logger logger = LoggerFactory.getLogger(EnergyCapability.class);
|
||||
|
||||
private int setPointDefaultDuration = -1;
|
||||
private final NetatmoDescriptionProvider descriptionProvider;
|
||||
|
||||
EnergyCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) {
|
||||
super(handler, EnergyApi.class);
|
||||
this.descriptionProvider = descriptionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateHomeData(HomeData homeData) {
|
||||
NAObjectMap<HomeDataRoom> rooms = homeData.getRooms();
|
||||
NAObjectMap<HomeDataModule> modules = homeData.getModules();
|
||||
handler.getActiveChildren().forEach(handler -> {
|
||||
HomeDataRoom roomData = rooms.get(handler.getId());
|
||||
if (roomData != null) {
|
||||
roomData.setIgnoredForThingUpdate(true);
|
||||
handler.setNewData(roomData);
|
||||
}
|
||||
HomeDataModule moduleData = modules.get(handler.getId());
|
||||
if (moduleData != null) {
|
||||
moduleData.setIgnoredForThingUpdate(true);
|
||||
handler.setNewData(moduleData);
|
||||
}
|
||||
});
|
||||
descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), GROUP_ENERGY, CHANNEL_PLANNING),
|
||||
homeData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName()))
|
||||
.collect(Collectors.toList()));
|
||||
setPointDefaultDuration = homeData.getThermSetpointDefaultDuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateHomeStatus(HomeStatus homeStatus) {
|
||||
NAObjectMap<Room> rooms = homeStatus.getRooms();
|
||||
NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
|
||||
handler.getActiveChildren().forEach(handler -> {
|
||||
Room roomData = rooms.get(handler.getId());
|
||||
if (roomData != null) {
|
||||
handler.setNewData(roomData);
|
||||
}
|
||||
HomeStatusModule data = modules.get(handler.getId());
|
||||
if (data != null) {
|
||||
handler.setNewData(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int getSetpointDefaultDuration() {
|
||||
return setPointDefaultDuration;
|
||||
}
|
||||
|
||||
public void setRoomThermMode(String roomId, SetpointMode targetMode) {
|
||||
getApi().ifPresent(api -> {
|
||||
try {
|
||||
api.setThermpoint(handler.getId(), roomId, targetMode,
|
||||
targetMode == SetpointMode.MAX ? setpointEndTimeFromNow(setPointDefaultDuration) : 0, 0);
|
||||
handler.expireData();
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error setting room thermostat mode '{}' : {}", targetMode, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setRoomThermTemp(String roomId, double temperature, long endtime, SetpointMode mode) {
|
||||
getApi().ifPresent(api -> {
|
||||
try {
|
||||
api.setThermpoint(handler.getId(), roomId, mode, endtime, temperature);
|
||||
handler.expireData();
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error setting room thermostat mode '{}' : {}", mode, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setRoomThermTemp(String roomId, double temperature) {
|
||||
setRoomThermTemp(roomId, temperature, setpointEndTimeFromNow(setPointDefaultDuration), SetpointMode.MANUAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(String channelName, Command command) {
|
||||
getApi().ifPresent(api -> {
|
||||
try {
|
||||
switch (channelName) {
|
||||
case CHANNEL_PLANNING:
|
||||
api.switchSchedule(handler.getId(), command.toString());
|
||||
break;
|
||||
case CHANNEL_SETPOINT_MODE:
|
||||
SetpointMode targetMode = SetpointMode.valueOf(command.toString());
|
||||
if (targetMode == SetpointMode.MANUAL) {
|
||||
logger.info("Switch to 'Manual' is done by setting a setpoint temp, command ignored");
|
||||
return;
|
||||
}
|
||||
api.setThermMode(handler.getId(), targetMode.apiDescriptor);
|
||||
break;
|
||||
}
|
||||
handler.expireData();
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error handling command '{}' : {}", command, e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Command '{}' sent to channel '{}' is not a valid setpoint mode.", command, channelName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static long setpointEndTimeFromNow(int duration_min) {
|
||||
return ZonedDateTime.now().plusMinutes(duration_min).toEpochSecond();
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet;
|
||||
|
||||
/**
|
||||
* {@link EventCapability} is the base class for handlers
|
||||
* subject to receive event notifications. This class registers to webhookservlet so
|
||||
* it can be notified when an event arrives.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EventCapability extends Capability {
|
||||
private Optional<NetatmoServlet> servlet = Optional.empty();
|
||||
|
||||
public EventCapability(CommonInterface handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
ApiBridgeHandler accountHandler = handler.getAccountHandler();
|
||||
if (accountHandler != null) {
|
||||
servlet = accountHandler.getServlet();
|
||||
servlet.ifPresent(s -> s.registerDataListener(handler.getId(), this));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
servlet.ifPresent(s -> s.unregisterDataListener(this));
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.HomeApi;
|
||||
import org.openhab.binding.netatmo.internal.api.NetatmoException;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeData;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.Location;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link HomeCapability} is the base class for handler able to manage persons and modules
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomeCapability extends RestCapability<HomeApi> {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomeCapability.class);
|
||||
|
||||
private final NetatmoDescriptionProvider descriptionProvider;
|
||||
|
||||
private NAObjectMap<HomeDataPerson> persons = new NAObjectMap<>();
|
||||
private NAObjectMap<HomeDataModule> modules = new NAObjectMap<>();
|
||||
|
||||
private Set<FeatureArea> featuresArea = Set.of();
|
||||
|
||||
public HomeCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) {
|
||||
super(handler, HomeApi.class);
|
||||
this.descriptionProvider = descriptionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateHomeData(HomeData home) {
|
||||
featuresArea = home.getFeatures();
|
||||
if (hasFeature(FeatureArea.SECURITY) && !handler.getCapabilities().containsKey(SecurityCapability.class)) {
|
||||
handler.getCapabilities().put(new SecurityCapability(handler));
|
||||
}
|
||||
if (hasFeature(FeatureArea.ENERGY) && !handler.getCapabilities().containsKey(EnergyCapability.class)) {
|
||||
handler.getCapabilities().put(new EnergyCapability(handler, descriptionProvider));
|
||||
}
|
||||
if (firstLaunch) {
|
||||
home.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country));
|
||||
home.getTimezone().map(tz -> properties.put(PROPERTY_TIMEZONE, tz));
|
||||
properties.put(GROUP_LOCATION, ((Location) home).getLocation().toString());
|
||||
properties.put(PROPERTY_FEATURE, featuresArea.stream().map(f -> f.name()).collect(Collectors.joining(",")));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterNewData(@Nullable NAObject newData) {
|
||||
super.afterNewData(newData);
|
||||
if (firstLaunch && !hasFeature(FeatureArea.SECURITY)) {
|
||||
handler.removeChannels(thing.getChannelsOfGroup(GROUP_SECURITY));
|
||||
}
|
||||
if (firstLaunch && !hasFeature(FeatureArea.ENERGY)) {
|
||||
handler.removeChannels(thing.getChannelsOfGroup(GROUP_ENERGY));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasFeature(FeatureArea seeked) {
|
||||
return featuresArea.contains(seeked);
|
||||
}
|
||||
|
||||
public NAObjectMap<HomeDataPerson> getPersons() {
|
||||
return persons;
|
||||
}
|
||||
|
||||
public NAObjectMap<HomeDataModule> getModules() {
|
||||
return modules;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<NAObject> updateReadings(HomeApi api) {
|
||||
List<NAObject> result = new ArrayList<>();
|
||||
try {
|
||||
HomeData homeData = api.getHomeData(handler.getId());
|
||||
if (homeData != null) {
|
||||
result.add(homeData);
|
||||
persons = homeData.getPersons();
|
||||
modules = homeData.getModules();
|
||||
}
|
||||
HomeStatus homeStatus = api.getHomeStatus(handler.getId());
|
||||
if (homeStatus != null) {
|
||||
result.add(homeStatus);
|
||||
}
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error gettting Home informations : {}", e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.EventChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
|
||||
|
||||
/**
|
||||
* {@link HomeSecurityThingCapability} is the ancestor of capabilities hosted by a security home
|
||||
* e.g. person and camera capabilities
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomeSecurityThingCapability extends Capability {
|
||||
protected final NetatmoDescriptionProvider descriptionProvider;
|
||||
protected final EventChannelHelper eventHelper;
|
||||
|
||||
protected Optional<SecurityCapability> securityCapability = Optional.empty();
|
||||
protected Optional<HomeCapability> homeCapability = Optional.empty();
|
||||
|
||||
public HomeSecurityThingCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
|
||||
List<ChannelHelper> channelHelpers) {
|
||||
super(handler);
|
||||
this.descriptionProvider = descriptionProvider;
|
||||
this.eventHelper = (EventChannelHelper) channelHelpers.stream().filter(c -> c instanceof EventChannelHelper)
|
||||
.findFirst().orElseThrow(() -> new IllegalArgumentException(
|
||||
"HomeSecurityThingCapability must find an EventChannelHelper, please file a bug report."));
|
||||
eventHelper.setModuleType(moduleType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
securityCapability = handler.getHomeCapability(SecurityCapability.class);
|
||||
homeCapability = handler.getHomeCapability(HomeCapability.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
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.api.NetatmoException;
|
||||
import org.openhab.binding.netatmo.internal.api.WeatherApi;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.config.MeasureConfiguration;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.MeasuresChannelHelper;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MeasureCapability} is the base class for handler able to handle user defined measures
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MeasureCapability extends RestCapability<WeatherApi> {
|
||||
private final Logger logger = LoggerFactory.getLogger(MeasureCapability.class);
|
||||
private final Map<String, State> measures = new HashMap<>();
|
||||
|
||||
public MeasureCapability(CommonInterface handler, List<ChannelHelper> helpers) {
|
||||
super(handler, WeatherApi.class);
|
||||
MeasuresChannelHelper measureChannelHelper = (MeasuresChannelHelper) helpers.stream()
|
||||
.filter(c -> c instanceof MeasuresChannelHelper).findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
"MeasureCapability must find a MeasuresChannelHelper, please file a bug report."));
|
||||
measureChannelHelper.setMeasures(measures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NAObject> updateReadings(WeatherApi api) {
|
||||
String bridgeId = handler.getBridgeId();
|
||||
String deviceId = bridgeId != null ? bridgeId : handler.getId();
|
||||
String moduleId = bridgeId != null ? handler.getId() : null;
|
||||
updateMeasures(api, deviceId, moduleId);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private void updateMeasures(WeatherApi api, String deviceId, @Nullable String moduleId) {
|
||||
measures.clear();
|
||||
handler.getActiveChannels().filter(channel -> !channel.getConfiguration().getProperties().isEmpty())
|
||||
.forEach(channel -> {
|
||||
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
|
||||
if (channelTypeUID != null) {
|
||||
MeasureConfiguration measureDef = channel.getConfiguration().as(MeasureConfiguration.class);
|
||||
String descriptor = channelTypeUID.getId().split("-")[0];
|
||||
try {
|
||||
Object result = measureDef.limit.isBlank()
|
||||
? api.getMeasures(deviceId, moduleId, measureDef.period, descriptor)
|
||||
: api.getMeasures(deviceId, moduleId, measureDef.period, descriptor,
|
||||
measureDef.limit);
|
||||
MeasureClass.AS_SET.stream().filter(mc -> mc.apiDescriptor.equals(descriptor)).findFirst()
|
||||
.ifPresent(mc -> {
|
||||
State state = result instanceof ZonedDateTime
|
||||
? toDateTimeType((ZonedDateTime) result)
|
||||
: result instanceof Double ? toQuantityType((Double) result, mc)
|
||||
: UnDefType.UNDEF;
|
||||
measures.put(channel.getUID().getIdWithoutGroup(), state);
|
||||
});
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error getting measures for channel {}, check configuration",
|
||||
channel.getLabel());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.data.EventType;
|
||||
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.Event;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.StateOption;
|
||||
|
||||
/**
|
||||
* {@link PersonCapability} gives the ability to handle Person specifics
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PersonCapability extends HomeSecurityThingCapability {
|
||||
private final ChannelUID cameraChannelUID;
|
||||
private @Nullable ZonedDateTime lastEventTime;
|
||||
|
||||
public PersonCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
|
||||
List<ChannelHelper> channelHelpers) {
|
||||
super(handler, descriptionProvider, channelHelpers);
|
||||
this.cameraChannelUID = new ChannelUID(thing.getUID(), GROUP_PERSON_EVENT, CHANNEL_EVENT_CAMERA_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeNewData() {
|
||||
super.beforeNewData();
|
||||
homeCapability.ifPresent(cap -> {
|
||||
Stream<HomeDataModule> cameras = cap.getModules().values().stream()
|
||||
.filter(module -> module.getType() == ModuleType.WELCOME);
|
||||
descriptionProvider.setStateOptions(cameraChannelUID,
|
||||
cameras.map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(String channelName, Command command) {
|
||||
if ((command instanceof OnOffType) && CHANNEL_PERSON_AT_HOME.equals(channelName)) {
|
||||
securityCapability.ifPresent(cap -> cap.setPersonAway(handler.getId(), OnOffType.OFF.equals(command)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEvent(Event event) {
|
||||
super.updateEvent(event);
|
||||
EventType eventType = event.getEventType();
|
||||
ZonedDateTime localLast = lastEventTime;
|
||||
ZonedDateTime eventTime = event.getTime();
|
||||
if ((localLast != null && !eventTime.isAfter(localLast)) || !eventType.appliesOn(ModuleType.PERSON)) {
|
||||
return; // ignore incoming events if they are deprecated
|
||||
}
|
||||
lastEventTime = eventTime;
|
||||
handler.triggerChannel(CHANNEL_HOME_EVENT,
|
||||
event.getSubTypeDescription().map(st -> st.name()).orElse(event.getEventType().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NAObject> updateReadings() {
|
||||
List<NAObject> result = new ArrayList<>();
|
||||
securityCapability.ifPresent(cap -> {
|
||||
Collection<HomeEvent> events = cap.getPersonEvents(handler.getId());
|
||||
if (!events.isEmpty()) {
|
||||
result.add(events.iterator().next());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.CHANNEL_FLOODLIGHT;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
|
||||
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link PresenceCapability} give to handle Presence Camera specifics
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PresenceCapability extends CameraCapability {
|
||||
private final Logger logger = LoggerFactory.getLogger(PresenceCapability.class);
|
||||
|
||||
public PresenceCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
|
||||
List<ChannelHelper> channelHelpers) {
|
||||
super(handler, descriptionProvider, channelHelpers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(String channelName, Command command) {
|
||||
if (CHANNEL_FLOODLIGHT.equals(channelName)) {
|
||||
if (command instanceof OnOffType) {
|
||||
changeFloodlightMode(command == OnOffType.ON ? FloodLightMode.ON : FloodLightMode.OFF);
|
||||
return;
|
||||
} else if (command instanceof StringType) {
|
||||
try {
|
||||
FloodLightMode mode = FloodLightMode.valueOf(command.toString());
|
||||
changeFloodlightMode(mode);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.info("Incorrect command '{}' received for channel '{}'", command, channelName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.handleCommand(channelName, command);
|
||||
}
|
||||
|
||||
private void changeFloodlightMode(FloodLightMode mode) {
|
||||
securityCapability.ifPresent(cap -> cap.changeFloodlightMode(localUrl, mode));
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import static java.time.temporal.ChronoUnit.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAThing;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link RefreshCapability} is the class used to embed the refreshing needs calculation for devices
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RefreshCapability extends Capability {
|
||||
private static final Duration DEFAULT_DELAY = Duration.of(20, SECONDS);
|
||||
private static final Duration PROBING_INTERVAL = Duration.of(120, SECONDS);
|
||||
private static final Duration OFFLINE_INTERVAL = Duration.of(15, MINUTES);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RefreshCapability.class);
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
private Duration dataValidity;
|
||||
private Instant dataTimeStamp = Instant.now();
|
||||
private Instant dataTimeStamp0 = Instant.MIN;
|
||||
private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
|
||||
private final boolean refreshConfigured;
|
||||
|
||||
public RefreshCapability(CommonInterface handler, ScheduledExecutorService scheduler, int refreshInterval) {
|
||||
super(handler);
|
||||
this.scheduler = scheduler;
|
||||
this.dataValidity = Duration.ofSeconds(Math.max(0, refreshInterval));
|
||||
this.refreshConfigured = !probing();
|
||||
freeJobAndReschedule(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
freeJobAndReschedule(0);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireData() {
|
||||
dataTimeStamp = Instant.now().minus(dataValidity);
|
||||
freeJobAndReschedule(1);
|
||||
}
|
||||
|
||||
private Duration dataAge() {
|
||||
return Duration.between(dataTimeStamp, Instant.now());
|
||||
}
|
||||
|
||||
private boolean probing() {
|
||||
return dataValidity.getSeconds() <= 0;
|
||||
}
|
||||
|
||||
private void proceedWithUpdate() {
|
||||
handler.proceedWithUpdate();
|
||||
long delay;
|
||||
if (!ThingStatus.ONLINE.equals(handler.getThing().getStatus())) {
|
||||
logger.debug("Module is not ONLINE; special refresh interval is used");
|
||||
delay = OFFLINE_INTERVAL.toSeconds();
|
||||
if (probing()) {
|
||||
dataTimeStamp0 = Instant.MIN;
|
||||
}
|
||||
} else if (refreshConfigured) {
|
||||
delay = dataValidity.getSeconds();
|
||||
} else {
|
||||
delay = (probing() ? PROBING_INTERVAL : dataValidity.minus(dataAge()).plus(DEFAULT_DELAY)).toSeconds();
|
||||
}
|
||||
delay = delay < 2 ? PROBING_INTERVAL.toSeconds() : delay;
|
||||
logger.debug("Module refreshed, next one in {} s", delay);
|
||||
freeJobAndReschedule(delay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateNAThing(NAThing newData) {
|
||||
super.updateNAThing(newData);
|
||||
newData.getLastSeen().ifPresent(timestamp -> {
|
||||
Instant tsInstant = timestamp.toInstant();
|
||||
if (probing()) {
|
||||
if (Instant.MIN.equals(dataTimeStamp0)) {
|
||||
dataTimeStamp0 = tsInstant;
|
||||
logger.debug("First data timestamp is {}", dataTimeStamp0);
|
||||
} else if (tsInstant.isAfter(dataTimeStamp0)) {
|
||||
dataValidity = Duration.between(dataTimeStamp0, tsInstant);
|
||||
logger.debug("Data validity period identified to be {}", dataValidity);
|
||||
} else {
|
||||
logger.debug("Data validity period not yet found - data timestamp unchanged");
|
||||
}
|
||||
}
|
||||
dataTimeStamp = tsInstant;
|
||||
});
|
||||
}
|
||||
|
||||
private void freeJobAndReschedule(long delay) {
|
||||
refreshJob.ifPresent(job -> job.cancel(false));
|
||||
refreshJob = delay == 0 ? Optional.empty()
|
||||
: Optional.of(scheduler.schedule(() -> proceedWithUpdate(), delay, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.RestManager;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.Device;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.Module;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
|
||||
/**
|
||||
* The {@link RestCapability} is the base class for handler capabilities
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class RestCapability<T extends RestManager> extends DeviceCapability {
|
||||
private Optional<T> api = Optional.empty();
|
||||
private Class<T> restManagerClass;
|
||||
|
||||
RestCapability(CommonInterface handler, Class<T> restManagerClazz) {
|
||||
super(handler);
|
||||
this.restManagerClass = restManagerClazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateNADevice(Device newData) {
|
||||
super.updateNADevice(newData);
|
||||
NAObjectMap<Module> modules = newData.getModules();
|
||||
handler.getActiveChildren().forEach(child -> {
|
||||
Module childData = modules.get(child.getId());
|
||||
if (childData != null) {
|
||||
child.setNewData(childData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<NAObject> updateReadings() {
|
||||
List<NAObject> result = new ArrayList<>();
|
||||
getApi().ifPresent(api -> result.addAll(updateReadings(api)));
|
||||
return result;
|
||||
}
|
||||
|
||||
protected List<NAObject> updateReadings(T api) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
protected Optional<T> getApi() {
|
||||
if (api.isEmpty()) {
|
||||
ApiBridgeHandler bridgeApi = handler.getAccountHandler();
|
||||
if (bridgeApi != null) {
|
||||
api = Optional.ofNullable(bridgeApi.getRestManager(restManagerClass));
|
||||
}
|
||||
}
|
||||
return api;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.commandToQuantity;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.action.RoomActions;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link RoomCapability} gives the ability to handle Room specifics
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RoomCapability extends Capability {
|
||||
private final Logger logger = LoggerFactory.getLogger(RoomCapability.class);
|
||||
private Optional<EnergyCapability> energyCapability = Optional.empty();
|
||||
|
||||
public RoomCapability(CommonInterface handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
energyCapability = handler.getHomeCapability(EnergyCapability.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(String channelName, Command command) {
|
||||
if (CHANNEL_SETPOINT_MODE.equals(channelName)) {
|
||||
try {
|
||||
SetpointMode targetMode = SetpointMode.valueOf(command.toString());
|
||||
if (targetMode == SetpointMode.MANUAL) {
|
||||
logger.info("Switch to 'Manual' mode is done by setting a setpoint temp, command ignored");
|
||||
} else {
|
||||
energyCapability.ifPresent(cap -> cap.setRoomThermMode(handler.getId(), targetMode));
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.info("Command '{}' is not a valid setpoint mode for channel '{}'", command, channelName);
|
||||
}
|
||||
} else if (CHANNEL_VALUE.equals(channelName)) {
|
||||
QuantityType<?> quantity = commandToQuantity(command, MeasureClass.INSIDE_TEMPERATURE);
|
||||
if (quantity != null) {
|
||||
energyCapability.ifPresent(cap -> cap.setRoomThermTemp(handler.getId(), quantity.doubleValue()));
|
||||
} else {
|
||||
logger.warn("Incorrect command '{}' on channel '{}'", command, channelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return List.of(RoomActions.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.api.NetatmoException;
|
||||
import org.openhab.binding.netatmo.internal.api.SecurityApi;
|
||||
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeData;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
|
||||
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SecurityCapability} is the base class for handler able to handle security features
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class SecurityCapability extends RestCapability<SecurityApi> {
|
||||
private final Logger logger = LoggerFactory.getLogger(SecurityCapability.class);
|
||||
|
||||
SecurityCapability(CommonInterface handler) {
|
||||
super(handler, SecurityApi.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateHomeData(HomeData homeData) {
|
||||
NAObjectMap<HomeDataPerson> persons = homeData.getPersons();
|
||||
NAObjectMap<HomeDataModule> modules = homeData.getModules();
|
||||
handler.getActiveChildren().forEach(childHandler -> {
|
||||
String childId = childHandler.getId();
|
||||
persons.getOpt(childId).ifPresentOrElse(person -> {
|
||||
person.setIgnoredForThingUpdate(true);
|
||||
childHandler.setNewData(person);
|
||||
}, () -> {
|
||||
modules.getOpt(childId).ifPresent(module -> {
|
||||
module.setIgnoredForThingUpdate(true);
|
||||
childHandler.setNewData(module);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateHomeStatus(HomeStatus homeStatus) {
|
||||
NAObjectMap<HomeStatusPerson> persons = homeStatus.getPersons();
|
||||
NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
|
||||
handler.getActiveChildren().forEach(childHandler -> {
|
||||
String childId = childHandler.getId();
|
||||
persons.getOpt(childId).ifPresentOrElse(person -> childHandler.setNewData(person), () -> {
|
||||
modules.getOpt(childId).ifPresentOrElse(module -> childHandler.setNewData(module), () -> {
|
||||
// This module is not present in the homestatus data, so it is considered as unreachable
|
||||
HomeStatusModule module = new HomeStatusModule();
|
||||
module.setReachable(false);
|
||||
childHandler.setNewData(module);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateHomeEvent(HomeEvent homeEvent) {
|
||||
String personId = homeEvent.getPersonId();
|
||||
if (personId != null) {
|
||||
handler.getActiveChildren().stream().filter(handler -> personId.equals(handler.getId())).findFirst()
|
||||
.ifPresent(handler -> {
|
||||
homeEvent.setIgnoredForThingUpdate(true);
|
||||
handler.setNewData(homeEvent);
|
||||
});
|
||||
}
|
||||
String cameraId = homeEvent.getCameraId();
|
||||
handler.getActiveChildren().stream().filter(handler -> cameraId.equals(handler.getId())).findFirst()
|
||||
.ifPresent(handler -> {
|
||||
homeEvent.setIgnoredForThingUpdate(true);
|
||||
handler.setNewData(homeEvent);
|
||||
});
|
||||
}
|
||||
|
||||
public Collection<HomeEvent> getCameraEvents(String cameraId) {
|
||||
return getApi().map(api -> {
|
||||
try {
|
||||
return api.getCameraEvents(handler.getId(), cameraId);
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error retrieving last events of camera '{}' : {}", cameraId, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}).orElse(List.of());
|
||||
}
|
||||
|
||||
public Collection<HomeEvent> getPersonEvents(String personId) {
|
||||
return getApi().map(api -> {
|
||||
try {
|
||||
return api.getPersonEvents(handler.getId(), personId);
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error retrieving last events of person '{}' : {}", personId, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}).orElse(List.of());
|
||||
}
|
||||
|
||||
public void setPersonAway(String personId, boolean away) {
|
||||
getApi().ifPresent(api -> {
|
||||
try {
|
||||
api.setPersonAwayStatus(handler.getId(), personId, away);
|
||||
handler.expireData();
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error setting person away/at home '{}' : {}", personId, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public @Nullable String ping(String vpnUrl) {
|
||||
return getApi().map(api -> {
|
||||
try {
|
||||
return api.ping(vpnUrl);
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error pinging camera '{}' : {}", vpnUrl, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
public void changeStatus(@Nullable String localURL, boolean status) {
|
||||
if (localURL == null) {
|
||||
logger.info("Monitoring changes can only be done on local camera.");
|
||||
return;
|
||||
}
|
||||
getApi().ifPresent(api -> {
|
||||
try {
|
||||
api.changeStatus(localURL, status);
|
||||
handler.expireData();
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error changing camera monitoring status '{}' : {}", status, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void changeFloodlightMode(@Nullable String localURL, FloodLightMode mode) {
|
||||
if (localURL == null) {
|
||||
logger.info("Changing floodlight mode can only be done on local camera.");
|
||||
return;
|
||||
}
|
||||
getApi().ifPresent(api -> {
|
||||
try {
|
||||
api.changeFloodLightMode(localURL, mode);
|
||||
handler.expireData();
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error changing Presence floodlight mode '{}' : {}", mode, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.capability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.netatmo.internal.api.NetatmoException;
|
||||
import org.openhab.binding.netatmo.internal.api.WeatherApi;
|
||||
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
|
||||
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link WeatherCapability} give the ability to read weather station api
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WeatherCapability extends RestCapability<WeatherApi> {
|
||||
private final Logger logger = LoggerFactory.getLogger(WeatherCapability.class);
|
||||
|
||||
public WeatherCapability(CommonInterface handler) {
|
||||
super(handler, WeatherApi.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<NAObject> updateReadings(WeatherApi api) {
|
||||
try {
|
||||
return List.of(api.getStationData(handler.getId()));
|
||||
} catch (NetatmoException e) {
|
||||
logger.warn("Error retrieving weather data '{}' : {}", handler.getId(), e.getMessage());
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user