added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.netatmo-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-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 dependency="true">mvn:org.openhab.osgiify/com.squareup.okhttp.okhttp/2.3.0</bundle>
|
||||
<bundle dependency="true">mvn:org.openhab.osgiify/com.squareup.okio.okio-1.3.0/1.3.0</bundle>
|
||||
<bundle dependency="true">mvn:org.openhab.osgiify/com.squareup.retrofit.retrofit/1.9.0</bundle>
|
||||
<bundle dependency="true">mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.client/1.0.0</bundle>
|
||||
<bundle dependency="true">mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.common/1.0.0</bundle>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.netatmo/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
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.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;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
|
||||
/**
|
||||
* 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, BigDecimal.ROUND_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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Dynamic provider of state options for NATherm1Handler.
|
||||
*
|
||||
* @author Gregory Moyer - Initial contribution
|
||||
* @author Gaël L'hopital - Ported as-is in Netatmo binding
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, NATherm1StateDescriptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class NATherm1StateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
|
||||
@Reference
|
||||
protected void setChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
|
||||
protected void unsetChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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.core.thing.ThingTypeUID;
|
||||
import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent.EventTypeEnum;
|
||||
|
||||
/**
|
||||
* The {@link NetatmoBinding} 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 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";
|
||||
|
||||
// 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";
|
||||
|
||||
// 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";
|
||||
|
||||
// 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<String> MEASURABLE_CHANNELS = Stream.of(new String[] {}).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());
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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.Map;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.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.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
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.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.Reference;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NetatmoHandlerFactory} is responsible for creating things and
|
||||
* thing handlers.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.netatmo")
|
||||
public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final Logger logger = LoggerFactory.getLogger(NetatmoHandlerFactory.class);
|
||||
private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
private final Map<ThingUID, @Nullable ServiceRegistration<?>> webHookServiceRegs = new HashMap<>();
|
||||
private final HttpService httpService;
|
||||
private final NATherm1StateDescriptionProvider stateDescriptionProvider;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final LocaleProvider localeProvider;
|
||||
private final TranslationProvider translationProvider;
|
||||
private boolean backgroundDiscovery;
|
||||
|
||||
@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;
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.localeProvider = localeProvider;
|
||||
this.translationProvider = translationProvider;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
logger.debug("backgroundDiscovery {}", backgroundDiscovery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof NetatmoBridgeHandler) {
|
||||
ThingUID thingUID = thingHandler.getThing().getUID();
|
||||
unregisterDeviceDiscoveryService(thingUID);
|
||||
unregisterWebHookServlet(thingUID);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void registerDeviceDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler) {
|
||||
if (bundleContext != null) {
|
||||
NetatmoModuleDiscoveryService discoveryService = new NetatmoModuleDiscoveryService(netatmoBridgeHandler,
|
||||
localeProvider, translationProvider);
|
||||
Map<String, @Nullable 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private synchronized void unregisterWebHookServlet(ThingUID thingUID) {
|
||||
ServiceRegistration<?> serviceReg = webHookServiceRegs.remove(thingUID);
|
||||
if (serviceReg != null) {
|
||||
serviceReg.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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,86 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This class holds various unit/measurement conversion methods
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
* @author Rob Nielsen - updated heat index
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WeatherUtils {
|
||||
|
||||
/**
|
||||
* Calculate the heat index using temperature and humidity
|
||||
* https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
|
||||
*
|
||||
* @param temperature in (°C)
|
||||
* @param humidity relative level (%)
|
||||
* @return heatIndex in (°C)
|
||||
*/
|
||||
public static double getHeatIndex(double temperature, double humidity) {
|
||||
double tempF = (temperature * 9.0 / 5.0) + 32.0; // calculations are done in Fahrenheit
|
||||
double heatIndex;
|
||||
if (tempF >= 80.0) {
|
||||
heatIndex = -42.379 + (2.04901523 * tempF) + (10.14333127 * humidity) - (0.22475541 * tempF * humidity)
|
||||
- (0.00683783 * tempF * tempF) - (0.05481717 * humidity * humidity)
|
||||
+ (0.00122874 * tempF * tempF * humidity) + (0.00085282 * tempF * humidity * humidity)
|
||||
- (0.00000199 * tempF * tempF * humidity * humidity);
|
||||
if (humidity < 13.0 && tempF <= 112.0) {
|
||||
heatIndex -= ((13.0 - humidity) / 4.0) * Math.sqrt((17.0 - Math.abs(tempF - 95.0)) / 17.0);
|
||||
} else if (humidity > 85.0 && tempF <= 87.0) {
|
||||
heatIndex += ((humidity - 85.0) / 10.0) * ((87.0 - tempF) / 5.0);
|
||||
}
|
||||
} else {
|
||||
heatIndex = 0.5 * (tempF + 61.0 + ((tempF - 68.0) * 1.2) + (humidity * 0.094));
|
||||
}
|
||||
|
||||
return (heatIndex - 32) * 5.0 / 9.0; // convert back to Celsius
|
||||
}
|
||||
|
||||
public static double getDewPointDep(double temperature, double dewpoint) {
|
||||
return temperature - dewpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the Dewpoint temperature given temperature and hygrometry
|
||||
* valid up to 60 degrees, from
|
||||
* http://en.wikipedia.org/wiki/Dew_point#Calculating_the_dew_point
|
||||
*
|
||||
* @param temperature in (°C)
|
||||
* @param humidity relative level (%)
|
||||
* @return dewpoint temperature
|
||||
*/
|
||||
public static double getDewPoint(double temperature, double humidity) {
|
||||
double a = 17.271, b = 237.2;
|
||||
double gamma = ((a * temperature) / (b + temperature)) + Math.log(humidity / 100.0);
|
||||
return b * gamma / (a - gamma);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the Humidex index given temperature and hygrometry
|
||||
*
|
||||
*
|
||||
* @param temperature in (°C)
|
||||
* @param hygro relative level (%)
|
||||
* @return Humidex index value
|
||||
*/
|
||||
public static double getHumidex(double temperature, double hygro) {
|
||||
double result = 6.112 * Math.pow(10, 7.5 * temperature / (237.7 + temperature)) * hygro / 100;
|
||||
result = temperature + 0.555555556 * (result - 10);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
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.openhab.core.io.net.http.HttpUtil;
|
||||
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.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 @Nullable CameraAddress cameraAddress;
|
||||
|
||||
protected CameraHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@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.getIsLocal())).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::getIsLocal).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();
|
||||
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 == null || address.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)).orElse(null);
|
||||
cameraAddress = address;
|
||||
}
|
||||
}
|
||||
return Optional.ofNullable(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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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.core.library.types.OnOffType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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,245 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoDataListener;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
|
||||
import io.swagger.client.model.*;
|
||||
|
||||
/**
|
||||
* 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, @Nullable 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 -> {
|
||||
dataBody.getDevices().forEach(station -> {
|
||||
discoverWeatherStation(station);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (netatmoBridgeHandler.configuration.readHealthyHomeCoach) {
|
||||
netatmoBridgeHandler.getHomecoachDataBody(null).ifPresent(dataBody -> {
|
||||
dataBody.getDevices().forEach(homecoach -> {
|
||||
discoverHomeCoach(homecoach);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (netatmoBridgeHandler.configuration.readThermostat) {
|
||||
netatmoBridgeHandler.getThermostatsDataBody(null).ifPresent(dataBody -> {
|
||||
dataBody.getDevices().forEach(plug -> {
|
||||
discoverThermostat(plug);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (netatmoBridgeHandler.configuration.readWelcome || netatmoBridgeHandler.configuration.readPresence) {
|
||||
netatmoBridgeHandler.getWelcomeDataBody(null).ifPresent(dataBody -> {
|
||||
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());
|
||||
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.getFavorite() != null && station.getFavorite();
|
||||
final String weatherStationName = createWeatherStationName(station, isFavorite);
|
||||
|
||||
onDeviceAddedInternal(station.getId(), null, station.getType(), weatherStationName, station.getFirmware());
|
||||
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
|
||||
if (!home.getCameras().isEmpty()) {
|
||||
onDeviceAddedInternal(home.getId(), null, WELCOME_HOME_THING_TYPE.getId(), home.getName(), null);
|
||||
// Discover Cameras
|
||||
home.getCameras().forEach(camera -> {
|
||||
onDeviceAddedInternal(camera.getId(), home.getId(), camera.getType(), camera.getName(), null);
|
||||
});
|
||||
|
||||
// Discover Known Persons
|
||||
home.getPersons().stream().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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.handler;
|
||||
|
||||
import static org.openhab.core.library.unit.MetricPrefix.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
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.core.config.core.Configuration;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
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.openhab.binding.netatmo.internal.channelhelper.BatteryHelper;
|
||||
import org.openhab.binding.netatmo.internal.channelhelper.RadioHelper;
|
||||
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 = SmartHomeUnits.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 = SmartHomeUnits.DEGREE_ANGLE;
|
||||
public static final Unit<Length> API_RAIN_UNIT = MILLI(SIUnits.METRE);
|
||||
public static final Unit<Dimensionless> API_CO2_UNIT = SmartHomeUnits.PARTS_PER_MILLION;
|
||||
public static final Unit<Dimensionless> API_NOISE_UNIT = SmartHomeUnits.DECIBEL;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractNetatmoThingHandler.class);
|
||||
|
||||
protected final TimeZoneProvider timeZoneProvider;
|
||||
protected final MeasurableChannels measurableChannels = new MeasurableChannels();
|
||||
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();
|
||||
|
||||
radioHelper = thing.getProperties().containsKey(PROPERTY_SIGNAL_LEVELS)
|
||||
? new RadioHelper(thing.getProperties().get(PROPERTY_SIGNAL_LEVELS))
|
||||
: null;
|
||||
batteryHelper = thing.getProperties().containsKey(PROPERTY_BATTERY_LEVELS)
|
||||
? new BatteryHelper(thing.getProperties().get(PROPERTY_BATTERY_LEVELS))
|
||||
: 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;
|
||||
|
||||
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();
|
||||
}
|
||||
result = measurableChannels.getNAThingProperty(channelId);
|
||||
|
||||
return result.orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
protected void updateChannels() {
|
||||
if (thing.getStatus() != ThingStatus.ONLINE) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateDataChannels();
|
||||
|
||||
triggerEventChannels();
|
||||
}
|
||||
|
||||
private void updateDataChannels() {
|
||||
getThing().getChannels().stream().filter(channel -> !channel.getKind().equals(ChannelKind.TRIGGER))
|
||||
.forEach(channel -> {
|
||||
|
||||
String channelId = channel.getUID().getId();
|
||||
if (isLinked(channelId)) {
|
||||
State state = getNAThingProperty(channelId);
|
||||
updateState(channel.getUID(), state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 -> channel.getKind().equals(ChannelKind.TRIGGER))
|
||||
.forEach(channel -> triggerChannelIfRequired(channel.getUID().getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 channelLinked(ChannelUID channelUID) {
|
||||
super.channelLinked(channelUID);
|
||||
measurableChannels.addChannel(channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelUnlinked(ChannelUID channelUID) {
|
||||
super.channelUnlinked(channelUID);
|
||||
measurableChannels.removeChannel(channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
logger.debug("Refreshing {}", channelUID);
|
||||
updateChannels();
|
||||
}
|
||||
}
|
||||
|
||||
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,91 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.MEASURABLE_CHANNELS;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
|
||||
|
||||
import io.swagger.client.CollectionFormats.CSVParams;
|
||||
import io.swagger.client.model.NAMeasureResponse;
|
||||
|
||||
/**
|
||||
* {@link MeasurableChannels} is a helper class designed to handle
|
||||
* manipulation of requests and responses provided by calls to
|
||||
* someNetatmoApi.getMeasures(....)
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MeasurableChannels {
|
||||
protected @Nullable NAMeasureResponse measures;
|
||||
protected List<String> measuredChannels = new ArrayList<>();
|
||||
|
||||
/*
|
||||
* If this channel value is provided as a measure, then add it
|
||||
* in the getMeasure parameter list
|
||||
*/
|
||||
protected void addChannel(ChannelUID channelUID) {
|
||||
String channel = channelUID.getId();
|
||||
if (MEASURABLE_CHANNELS.contains(channel)) {
|
||||
measuredChannels.add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If this channel value is provided as a measure, then delete
|
||||
* it in the getMeasure parameter list
|
||||
*/
|
||||
protected void removeChannel(ChannelUID channelUID) {
|
||||
String channel = channelUID.getId();
|
||||
measuredChannels.remove(channel);
|
||||
}
|
||||
|
||||
protected Optional<State> getNAThingProperty(String channelId) {
|
||||
int index = measuredChannels.indexOf(channelId);
|
||||
NAMeasureResponse theMeasures = measures;
|
||||
if (index != -1 && theMeasures != null) {
|
||||
if (!theMeasures.getBody().isEmpty()) {
|
||||
List<List<Float>> valueList = theMeasures.getBody().get(0).getValue();
|
||||
if (!valueList.isEmpty()) {
|
||||
List<Float> values = valueList.get(0);
|
||||
if (values.size() >= index) {
|
||||
Float value = values.get(index);
|
||||
return Optional.of(ChannelTypeUtils.toDecimalType(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<CSVParams> getAsCsv() {
|
||||
if (!measuredChannels.isEmpty()) {
|
||||
return Optional.of(new CSVParams(measuredChannels));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public void setMeasures(NAMeasureResponse measures) {
|
||||
this.measures = measures;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
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.request.OAuthClientRequest;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
|
||||
import io.swagger.client.ApiClient;
|
||||
import io.swagger.client.CollectionFormats.CSVParams;
|
||||
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.OAuth;
|
||||
import io.swagger.client.auth.OAuthFlow;
|
||||
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;
|
||||
import retrofit.RestAdapter.LogLevel;
|
||||
import retrofit.RetrofitError;
|
||||
import retrofit.RetrofitError.Kind;
|
||||
|
||||
/**
|
||||
* {@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 APIMap apiMap;
|
||||
private @Nullable WelcomeWebHookServlet webHookServlet;
|
||||
private List<NetatmoDataListener> dataListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
private class APIMap extends HashMap<Class<?>, Object> {
|
||||
private static final long serialVersionUID = -2024031764691952343L;
|
||||
private ApiClient apiClient;
|
||||
|
||||
public APIMap(ApiClient apiClient) {
|
||||
super();
|
||||
this.apiClient = apiClient;
|
||||
}
|
||||
|
||||
public Object get(Class<?> apiClass) {
|
||||
if (!super.containsKey(apiClass)) {
|
||||
Object api = apiClient.createService(apiClass);
|
||||
super.put(apiClass, api);
|
||||
}
|
||||
return super.get(apiClass);
|
||||
}
|
||||
}
|
||||
|
||||
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 (RetrofitError e) {
|
||||
if (e.getKind() == Kind.NETWORK) {
|
||||
logger.warn("Network error while connecting to Netatmo API, will retry in {} s",
|
||||
configuration.reconnectInterval);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Netatmo Access Failed, will retry in " + configuration.reconnectInterval + " seconds.");
|
||||
} else {
|
||||
switch (e.getResponse().getStatus()) {
|
||||
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());
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Unable to connect Netatmo API : {}", e.getMessage(), e);
|
||||
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() throws RetrofitError {
|
||||
ApiClient apiClient = new ApiClient();
|
||||
|
||||
OAuth auth = new OAuth(new OkHttpClient(),
|
||||
OAuthClientRequest.tokenLocation("https://api.netatmo.net/oauth2/token"));
|
||||
auth.setFlow(OAuthFlow.password);
|
||||
auth.setAuthenticationRequestBuilder(OAuthClientRequest.authorizationLocation(""));
|
||||
|
||||
apiClient.getApiAuthorizations().put("password_oauth", auth);
|
||||
apiClient.getTokenEndPoint().setClientId(configuration.clientId).setClientSecret(configuration.clientSecret)
|
||||
.setUsername(configuration.username).setPassword(configuration.password).setScope(getApiScope());
|
||||
|
||||
apiClient.configureFromOkclient(new OkHttpClient());
|
||||
apiClient.getAdapterBuilder().setLogLevel(logger.isDebugEnabled() ? LogLevel.FULL : LogLevel.NONE);
|
||||
|
||||
apiMap = new APIMap(apiClient);
|
||||
}
|
||||
|
||||
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() {
|
||||
APIMap map = apiMap;
|
||||
return map != null ? (PartnerApi) map.get(PartnerApi.class) : null;
|
||||
}
|
||||
|
||||
public Optional<StationApi> getStationApi() {
|
||||
APIMap map = apiMap;
|
||||
return map != null ? Optional.of((StationApi) map.get(StationApi.class)) : Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<HealthyhomecoachApi> getHomeCoachApi() {
|
||||
APIMap map = apiMap;
|
||||
return map != null ? Optional.of((HealthyhomecoachApi) map.get(HealthyhomecoachApi.class)) : Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ThermostatApi> getThermostatApi() {
|
||||
APIMap map = apiMap;
|
||||
return map != null ? Optional.of((ThermostatApi) map.get(ThermostatApi.class)) : Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<WelcomeApi> getWelcomeApi() {
|
||||
APIMap map = apiMap;
|
||||
return map != null ? Optional.of((WelcomeApi) map.get(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, new CSVParams(types), moduleId, null, "last", 1, true, false).getBody())
|
||||
.orElse(null);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
NAMeasureBodyElem element = (data != null && data.size() > 0) ? 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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);
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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.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.openhab.binding.netatmo.internal.ChannelTypeUtils;
|
||||
import org.openhab.binding.netatmo.internal.RefreshStrategy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.swagger.client.model.NAPlace;
|
||||
import retrofit.RetrofitError;
|
||||
|
||||
/**
|
||||
* {@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 (RetrofitError 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.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.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.openhab.binding.netatmo.internal.ChannelTypeUtils;
|
||||
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,117 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.homecoach;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
|
||||
|
||||
import io.swagger.client.model.NADashboardData;
|
||||
import io.swagger.client.model.NAHealthyHomeCoach;
|
||||
|
||||
/**
|
||||
* {@link NAHealthyHomeCoachHandler} is the class used to handle the Health Home Coach device
|
||||
*
|
||||
* @author Michael Svinth - Initial contribution OH2 version
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAHealthyHomeCoachHandler extends NetatmoDeviceHandler<NAHealthyHomeCoach> {
|
||||
|
||||
public NAHealthyHomeCoachHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<NAHealthyHomeCoach> updateReadings() {
|
||||
return getBridgeHandler().flatMap(handler -> handler.getHomecoachDataBody(getId()))
|
||||
.map(dataBody -> dataBody.getDevices().stream()
|
||||
.filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProperties(NAHealthyHomeCoach deviceData) {
|
||||
updateProperties(deviceData.getFirmware(), deviceData.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
NADashboardData dashboardData = getDevice().map(d -> d.getDashboardData()).orElse(null);
|
||||
if (dashboardData != null) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_CO2:
|
||||
return toQuantityType(dashboardData.getCO2(), API_CO2_UNIT);
|
||||
case CHANNEL_TEMPERATURE:
|
||||
return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_HEALTH_INDEX:
|
||||
return toStringType(toHealthIndexString(dashboardData.getHealthIdx()));
|
||||
case CHANNEL_MIN_TEMP:
|
||||
return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_MAX_TEMP:
|
||||
return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_TEMP_TREND:
|
||||
return toStringType(dashboardData.getTempTrend());
|
||||
case CHANNEL_NOISE:
|
||||
return toQuantityType(dashboardData.getNoise(), API_NOISE_UNIT);
|
||||
case CHANNEL_PRESSURE:
|
||||
return toQuantityType(dashboardData.getPressure(), API_PRESSURE_UNIT);
|
||||
case CHANNEL_PRESS_TREND:
|
||||
return toStringType(dashboardData.getPressureTrend());
|
||||
case CHANNEL_ABSOLUTE_PRESSURE:
|
||||
return toQuantityType(dashboardData.getAbsolutePressure(), API_PRESSURE_UNIT);
|
||||
case CHANNEL_TIMEUTC:
|
||||
return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_DATE_MIN_TEMP:
|
||||
return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_DATE_MAX_TEMP:
|
||||
return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_HUMIDITY:
|
||||
return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
|
||||
}
|
||||
}
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
private @Nullable String toHealthIndexString(@Nullable Integer healthIndex) {
|
||||
if (healthIndex == null) {
|
||||
return null;
|
||||
}
|
||||
switch (healthIndex) {
|
||||
case 0:
|
||||
return "healthy";
|
||||
case 1:
|
||||
return "fine";
|
||||
case 2:
|
||||
return "fair";
|
||||
case 3:
|
||||
return "poor";
|
||||
case 4:
|
||||
return "unhealthy";
|
||||
default:
|
||||
return healthIndex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> getDataTimestamp() {
|
||||
return getDevice().map(d -> d.getLastStatusStore());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.presence;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.toOnOffType;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
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.openhab.binding.netatmo.internal.camera.CameraHandler;
|
||||
|
||||
import io.swagger.client.model.NAWelcomeCamera;
|
||||
|
||||
/**
|
||||
* {@link NAPresenceCameraHandler} is the class used to handle Presence camera data
|
||||
*
|
||||
* @author Sven Strohschein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAPresenceCameraHandler extends CameraHandler {
|
||||
|
||||
private static final String FLOODLIGHT_SET_URL_PATH = "/command/floodlight_set_config";
|
||||
|
||||
private State floodlightAutoModeState = UnDefType.UNDEF;
|
||||
|
||||
public NAPresenceCameraHandler(final Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
String channelId = channelUID.getId();
|
||||
switch (channelId) {
|
||||
case CHANNEL_CAMERA_FLOODLIGHT:
|
||||
if (command == OnOffType.ON) {
|
||||
switchFloodlight(true);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
switchFloodlight(false);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE:
|
||||
if (command == OnOffType.ON) {
|
||||
switchFloodlightAutoMode(true);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
switchFloodlightAutoMode(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
super.handleCommand(channelUID, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_CAMERA_FLOODLIGHT:
|
||||
return getFloodlightState();
|
||||
case CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE:
|
||||
// The auto-mode state shouldn't be updated, because this isn't a dedicated information. When the
|
||||
// floodlight is switched on the state within the Netatmo API is "on" and the information if the
|
||||
// previous
|
||||
// state was "auto" instead of "off" is lost... Therefore the binding handles its own auto-mode state.
|
||||
if (floodlightAutoModeState == UnDefType.UNDEF) {
|
||||
floodlightAutoModeState = getFloodlightAutoModeState();
|
||||
}
|
||||
return floodlightAutoModeState;
|
||||
}
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
private State getFloodlightState() {
|
||||
return getModule().map(m -> toOnOffType(m.getLightModeStatus() == NAWelcomeCamera.LightModeStatusEnum.ON))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
private State getFloodlightAutoModeState() {
|
||||
return getModule().map(m -> toOnOffType(m.getLightModeStatus() == NAWelcomeCamera.LightModeStatusEnum.AUTO))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
private void switchFloodlight(boolean isOn) {
|
||||
if (isOn) {
|
||||
changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.ON);
|
||||
} else {
|
||||
switchFloodlightAutoMode(floodlightAutoModeState == OnOffType.ON);
|
||||
}
|
||||
}
|
||||
|
||||
private void switchFloodlightAutoMode(boolean isAutoMode) {
|
||||
floodlightAutoModeState = toOnOffType(isAutoMode);
|
||||
if (isAutoMode) {
|
||||
changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.AUTO);
|
||||
} else {
|
||||
changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
private void changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum mode) {
|
||||
Optional<String> localCameraURL = getLocalCameraURL();
|
||||
if (localCameraURL.isPresent()) {
|
||||
String url = localCameraURL.get() + FLOODLIGHT_SET_URL_PATH + "?config=%7B%22mode%22:%22" + mode.toString()
|
||||
+ "%22%7D";
|
||||
executeGETRequest(url);
|
||||
|
||||
invalidateParentCacheAndRefresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.station;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.binding.netatmo.internal.WeatherUtils;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
|
||||
|
||||
import io.swagger.client.model.NADashboardData;
|
||||
import io.swagger.client.model.NAMain;
|
||||
|
||||
/**
|
||||
* {@link NAMainHandler} is the base class for all current Netatmo
|
||||
* weather station equipments (both modules and devices)
|
||||
*
|
||||
* @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 NAMainHandler extends NetatmoDeviceHandler<NAMain> {
|
||||
private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
|
||||
|
||||
public NAMainHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<NAMain> updateReadings() {
|
||||
Optional<NAMain> result = getBridgeHandler().flatMap(handler -> handler.getStationsDataBody(getId()))
|
||||
.map(dataBody -> dataBody.getDevices().stream()
|
||||
.filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
|
||||
result.ifPresent(device -> {
|
||||
device.getModules().forEach(child -> childs.put(child.getId(), child));
|
||||
});
|
||||
|
||||
updateMeasurements();
|
||||
|
||||
childs.keySet().forEach((childId) -> {
|
||||
findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> {
|
||||
naChildModule.updateMeasurements();
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProperties(NAMain deviceData) {
|
||||
updateProperties(deviceData.getFirmware(), deviceData.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMeasurements() {
|
||||
updateDayMeasurements();
|
||||
updateWeekMeasurements();
|
||||
updateMonthMeasurements();
|
||||
}
|
||||
|
||||
private void updateDayMeasurements() {
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> types = new ArrayList<>();
|
||||
addMeasurement(channels, types, CHANNEL_MIN_CO2, MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_CO2, MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY, MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY, MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_NOISE, MIN_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_NOISE, MAX_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_PRESSURE, MIN_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_PRESSURE, MAX_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2, DATE_MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2, DATE_MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY, DATE_MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY, DATE_MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_NOISE, DATE_MIN_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_NOISE, DATE_MAX_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_PRESSURE, DATE_MIN_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_PRESSURE, DATE_MAX_PRESSURE);
|
||||
if (!channels.isEmpty()) {
|
||||
getMeasurements(getId(), null, ONE_DAY, types, channels, channelMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWeekMeasurements() {
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> types = new ArrayList<>();
|
||||
addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_WEEK, MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_WEEK, MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_WEEK, MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_WEEK, MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_NOISE_THIS_WEEK, MIN_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_NOISE_THIS_WEEK, MAX_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_PRESSURE_THIS_WEEK, MIN_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_PRESSURE_THIS_WEEK, MAX_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_WEEK, MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_WEEK, MAX_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_WEEK, DATE_MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_WEEK, DATE_MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK, DATE_MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK, DATE_MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_NOISE_THIS_WEEK, DATE_MIN_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_NOISE_THIS_WEEK, DATE_MAX_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_PRESSURE_THIS_WEEK, DATE_MIN_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_PRESSURE_THIS_WEEK, DATE_MAX_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_WEEK, DATE_MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_WEEK, DATE_MAX_TEMP);
|
||||
if (!channels.isEmpty()) {
|
||||
getMeasurements(getId(), null, ONE_WEEK, types, channels, channelMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMonthMeasurements() {
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> types = new ArrayList<>();
|
||||
addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_MONTH, MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_MONTH, MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_MONTH, MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_MONTH, MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_NOISE_THIS_MONTH, MIN_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_NOISE_THIS_MONTH, MAX_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_PRESSURE_THIS_MONTH, MIN_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_PRESSURE_THIS_MONTH, MAX_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_MONTH, MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_MONTH, MAX_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_MONTH, DATE_MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_MONTH, DATE_MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH, DATE_MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH, DATE_MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_NOISE_THIS_MONTH, DATE_MIN_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_NOISE_THIS_MONTH, DATE_MAX_NOISE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_PRESSURE_THIS_MONTH, DATE_MIN_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_PRESSURE_THIS_MONTH, DATE_MAX_PRESSURE);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_MONTH, DATE_MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_MONTH, DATE_MAX_TEMP);
|
||||
if (!channels.isEmpty()) {
|
||||
getMeasurements(getId(), null, ONE_MONTH, types, channels, channelMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
NADashboardData dashboardData = getDevice().map(d -> d.getDashboardData()).orElse(null);
|
||||
if (dashboardData != null) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_CO2:
|
||||
return toQuantityType(dashboardData.getCO2(), API_CO2_UNIT);
|
||||
case CHANNEL_TEMPERATURE:
|
||||
return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_MIN_TEMP:
|
||||
return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_MAX_TEMP:
|
||||
return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_TEMP_TREND:
|
||||
return toStringType(dashboardData.getTempTrend());
|
||||
case CHANNEL_NOISE:
|
||||
return toQuantityType(dashboardData.getNoise(), API_NOISE_UNIT);
|
||||
case CHANNEL_PRESSURE:
|
||||
return toQuantityType(dashboardData.getPressure(), API_PRESSURE_UNIT);
|
||||
case CHANNEL_PRESS_TREND:
|
||||
return toStringType(dashboardData.getPressureTrend());
|
||||
case CHANNEL_ABSOLUTE_PRESSURE:
|
||||
return toQuantityType(dashboardData.getAbsolutePressure(), API_PRESSURE_UNIT);
|
||||
case CHANNEL_TIMEUTC:
|
||||
return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_DATE_MIN_TEMP:
|
||||
return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_DATE_MAX_TEMP:
|
||||
return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_HUMIDITY:
|
||||
return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
|
||||
case CHANNEL_HUMIDEX:
|
||||
return toDecimalType(
|
||||
WeatherUtils.getHumidex(dashboardData.getTemperature(), dashboardData.getHumidity()));
|
||||
case CHANNEL_HEATINDEX:
|
||||
return toQuantityType(
|
||||
WeatherUtils.getHeatIndex(dashboardData.getTemperature(), dashboardData.getHumidity()),
|
||||
API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DEWPOINT:
|
||||
return toQuantityType(
|
||||
WeatherUtils.getDewPoint(dashboardData.getTemperature(), dashboardData.getHumidity()),
|
||||
API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DEWPOINTDEP:
|
||||
Double dewPoint = WeatherUtils.getDewPoint(dashboardData.getTemperature(),
|
||||
dashboardData.getHumidity());
|
||||
return toQuantityType(WeatherUtils.getDewPointDep(dashboardData.getTemperature(), dewPoint),
|
||||
API_TEMPERATURE_UNIT);
|
||||
}
|
||||
}
|
||||
|
||||
switch (channelId) {
|
||||
case CHANNEL_MIN_CO2:
|
||||
case CHANNEL_MIN_CO2_THIS_WEEK:
|
||||
case CHANNEL_MIN_CO2_THIS_MONTH:
|
||||
case CHANNEL_MAX_CO2:
|
||||
case CHANNEL_MAX_CO2_THIS_WEEK:
|
||||
case CHANNEL_MAX_CO2_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_CO2_UNIT);
|
||||
case CHANNEL_MIN_HUMIDITY:
|
||||
case CHANNEL_MIN_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_MIN_HUMIDITY_THIS_MONTH:
|
||||
case CHANNEL_MAX_HUMIDITY:
|
||||
case CHANNEL_MAX_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_MAX_HUMIDITY_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_HUMIDITY_UNIT);
|
||||
case CHANNEL_MIN_NOISE:
|
||||
case CHANNEL_MIN_NOISE_THIS_WEEK:
|
||||
case CHANNEL_MIN_NOISE_THIS_MONTH:
|
||||
case CHANNEL_MAX_NOISE:
|
||||
case CHANNEL_MAX_NOISE_THIS_WEEK:
|
||||
case CHANNEL_MAX_NOISE_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_NOISE_UNIT);
|
||||
case CHANNEL_MIN_PRESSURE:
|
||||
case CHANNEL_MIN_PRESSURE_THIS_WEEK:
|
||||
case CHANNEL_MIN_PRESSURE_THIS_MONTH:
|
||||
case CHANNEL_MAX_PRESSURE:
|
||||
case CHANNEL_MAX_PRESSURE_THIS_WEEK:
|
||||
case CHANNEL_MAX_PRESSURE_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_PRESSURE_UNIT);
|
||||
case CHANNEL_MIN_TEMP_THIS_WEEK:
|
||||
case CHANNEL_MIN_TEMP_THIS_MONTH:
|
||||
case CHANNEL_MAX_TEMP_THIS_WEEK:
|
||||
case CHANNEL_MAX_TEMP_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DATE_MIN_CO2:
|
||||
case CHANNEL_DATE_MIN_CO2_THIS_WEEK:
|
||||
case CHANNEL_DATE_MIN_CO2_THIS_MONTH:
|
||||
case CHANNEL_DATE_MAX_CO2:
|
||||
case CHANNEL_DATE_MAX_CO2_THIS_WEEK:
|
||||
case CHANNEL_DATE_MAX_CO2_THIS_MONTH:
|
||||
case CHANNEL_DATE_MIN_NOISE:
|
||||
case CHANNEL_DATE_MIN_NOISE_THIS_WEEK:
|
||||
case CHANNEL_DATE_MIN_NOISE_THIS_MONTH:
|
||||
case CHANNEL_DATE_MAX_NOISE:
|
||||
case CHANNEL_DATE_MAX_NOISE_THIS_WEEK:
|
||||
case CHANNEL_DATE_MAX_NOISE_THIS_MONTH:
|
||||
case CHANNEL_DATE_MIN_HUMIDITY:
|
||||
case CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH:
|
||||
case CHANNEL_DATE_MAX_HUMIDITY:
|
||||
case CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH:
|
||||
case CHANNEL_DATE_MIN_PRESSURE:
|
||||
case CHANNEL_DATE_MIN_PRESSURE_THIS_WEEK:
|
||||
case CHANNEL_DATE_MIN_PRESSURE_THIS_MONTH:
|
||||
case CHANNEL_DATE_MAX_PRESSURE:
|
||||
case CHANNEL_DATE_MAX_PRESSURE_THIS_WEEK:
|
||||
case CHANNEL_DATE_MAX_PRESSURE_THIS_MONTH:
|
||||
case CHANNEL_DATE_MIN_TEMP_THIS_WEEK:
|
||||
case CHANNEL_DATE_MIN_TEMP_THIS_MONTH:
|
||||
case CHANNEL_DATE_MAX_TEMP_THIS_WEEK:
|
||||
case CHANNEL_DATE_MAX_TEMP_THIS_MONTH:
|
||||
return toDateTimeType(channelMeasurements.get(channelId), timeZoneProvider.getTimeZone());
|
||||
}
|
||||
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> getDataTimestamp() {
|
||||
return getDevice().map(d -> d.getLastStatusStore());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReachable() {
|
||||
boolean result = false;
|
||||
Optional<NAMain> device = getDevice();
|
||||
if (device.isPresent()) {
|
||||
Boolean reachable = device.get().getReachable();
|
||||
result = reachable != null ? reachable.booleanValue() : false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.station;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.binding.netatmo.internal.WeatherUtils;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
|
||||
|
||||
import io.swagger.client.model.NADashboardData;
|
||||
import io.swagger.client.model.NAStationModule;
|
||||
|
||||
/**
|
||||
* {@link NAModule1Handler} is the class used to handle the outdoor module
|
||||
* capable of reporting temperature and humidity
|
||||
*
|
||||
* @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 NAModule1Handler extends NetatmoModuleHandler<NAStationModule> {
|
||||
private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
|
||||
|
||||
public NAModule1Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProperties(NAStationModule moduleData) {
|
||||
updateProperties(moduleData.getFirmware(), moduleData.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMeasurements() {
|
||||
updateDayMeasurements();
|
||||
updateWeekMeasurements();
|
||||
updateMonthMeasurements();
|
||||
}
|
||||
|
||||
private void updateDayMeasurements() {
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> types = new ArrayList<>();
|
||||
addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY, MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY, MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY, DATE_MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY, DATE_MAX_HUM);
|
||||
if (!channels.isEmpty()) {
|
||||
getMeasurements(getParentId(), getId(), ONE_DAY, types, channels, channelMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWeekMeasurements() {
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> types = new ArrayList<>();
|
||||
addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_WEEK, MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_WEEK, MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_WEEK, MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_WEEK, MAX_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK, DATE_MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK, DATE_MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_WEEK, DATE_MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_WEEK, DATE_MAX_TEMP);
|
||||
if (!channels.isEmpty()) {
|
||||
getMeasurements(getParentId(), getId(), ONE_WEEK, types, channels, channelMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMonthMeasurements() {
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> types = new ArrayList<>();
|
||||
addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_MONTH, MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_MONTH, MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_MONTH, MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_MONTH, MAX_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH, DATE_MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH, DATE_MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_MONTH, DATE_MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_MONTH, DATE_MAX_TEMP);
|
||||
if (!channels.isEmpty()) {
|
||||
getMeasurements(getParentId(), getId(), ONE_MONTH, types, channels, channelMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
|
||||
if (dashboardData != null) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_TEMP_TREND:
|
||||
return toStringType(dashboardData.getTempTrend());
|
||||
case CHANNEL_TEMPERATURE:
|
||||
return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DATE_MIN_TEMP:
|
||||
return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_DATE_MAX_TEMP:
|
||||
return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_MIN_TEMP:
|
||||
return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_MAX_TEMP:
|
||||
return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_HUMIDITY:
|
||||
return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
|
||||
case CHANNEL_TIMEUTC:
|
||||
return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_HUMIDEX:
|
||||
return toDecimalType(
|
||||
WeatherUtils.getHumidex(dashboardData.getTemperature(), dashboardData.getHumidity()));
|
||||
case CHANNEL_HEATINDEX:
|
||||
return toQuantityType(
|
||||
WeatherUtils.getHeatIndex(dashboardData.getTemperature(), dashboardData.getHumidity()),
|
||||
API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DEWPOINT:
|
||||
return toQuantityType(
|
||||
WeatherUtils.getDewPoint(dashboardData.getTemperature(), dashboardData.getHumidity()),
|
||||
API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DEWPOINTDEP:
|
||||
Double dewpoint = WeatherUtils.getDewPoint(dashboardData.getTemperature(),
|
||||
dashboardData.getHumidity());
|
||||
return toQuantityType(WeatherUtils.getDewPointDep(dashboardData.getTemperature(), dewpoint),
|
||||
API_TEMPERATURE_UNIT);
|
||||
}
|
||||
}
|
||||
|
||||
switch (channelId) {
|
||||
case CHANNEL_MIN_HUMIDITY:
|
||||
case CHANNEL_MIN_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_MIN_HUMIDITY_THIS_MONTH:
|
||||
case CHANNEL_MAX_HUMIDITY:
|
||||
case CHANNEL_MAX_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_MAX_HUMIDITY_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_HUMIDITY_UNIT);
|
||||
case CHANNEL_MIN_TEMP_THIS_WEEK:
|
||||
case CHANNEL_MIN_TEMP_THIS_MONTH:
|
||||
case CHANNEL_MAX_TEMP_THIS_WEEK:
|
||||
case CHANNEL_MAX_TEMP_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DATE_MIN_HUMIDITY:
|
||||
case CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH:
|
||||
case CHANNEL_DATE_MAX_HUMIDITY:
|
||||
case CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH:
|
||||
case CHANNEL_DATE_MIN_TEMP_THIS_WEEK:
|
||||
case CHANNEL_DATE_MIN_TEMP_THIS_MONTH:
|
||||
case CHANNEL_DATE_MAX_TEMP_THIS_WEEK:
|
||||
case CHANNEL_DATE_MAX_TEMP_THIS_MONTH:
|
||||
return toDateTimeType(channelMeasurements.get(channelId), timeZoneProvider.getTimeZone());
|
||||
}
|
||||
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReachable() {
|
||||
boolean result = false;
|
||||
Optional<NAStationModule> module = getModule();
|
||||
if (module.isPresent()) {
|
||||
Boolean reachable = module.get().getReachable();
|
||||
result = reachable != null ? reachable.booleanValue() : false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.station;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
|
||||
|
||||
import io.swagger.client.model.NADashboardData;
|
||||
import io.swagger.client.model.NAStationModule;
|
||||
|
||||
/**
|
||||
* {@link NAModule2Handler} is the class used to handle the wind module
|
||||
* capable of reporting wind angle and strength
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAModule2Handler extends NetatmoModuleHandler<NAStationModule> {
|
||||
|
||||
public NAModule2Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProperties(NAStationModule moduleData) {
|
||||
updateProperties(moduleData.getFirmware(), moduleData.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
|
||||
if (dashboardData != null) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_WIND_ANGLE:
|
||||
return toQuantityType(dashboardData.getWindAngle(), API_WIND_DIRECTION_UNIT);
|
||||
case CHANNEL_WIND_STRENGTH:
|
||||
return toQuantityType(dashboardData.getWindStrength(), API_WIND_SPEED_UNIT);
|
||||
case CHANNEL_GUST_ANGLE:
|
||||
return toQuantityType(dashboardData.getGustAngle(), API_WIND_DIRECTION_UNIT);
|
||||
case CHANNEL_GUST_STRENGTH:
|
||||
return toQuantityType(dashboardData.getGustStrength(), API_WIND_SPEED_UNIT);
|
||||
case CHANNEL_TIMEUTC:
|
||||
return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_MAX_WIND_STRENGTH:
|
||||
return toQuantityType(dashboardData.getMaxWindStr(), API_WIND_SPEED_UNIT);
|
||||
case CHANNEL_DATE_MAX_WIND_STRENGTH:
|
||||
return toDateTimeType(dashboardData.getDateMaxWindStr(), timeZoneProvider.getTimeZone());
|
||||
}
|
||||
}
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReachable() {
|
||||
boolean result = false;
|
||||
Optional<NAStationModule> module = getModule();
|
||||
if (module.isPresent()) {
|
||||
Boolean reachable = module.get().getReachable();
|
||||
result = reachable != null ? reachable.booleanValue() : false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.station;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
|
||||
|
||||
import io.swagger.client.model.NADashboardData;
|
||||
import io.swagger.client.model.NAStationModule;
|
||||
|
||||
/**
|
||||
* {@link NAModule3Handler} is the class used to handle the Rain Gauge
|
||||
* capable of measuring precipitation
|
||||
*
|
||||
* @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 NAModule3Handler extends NetatmoModuleHandler<NAStationModule> {
|
||||
private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
|
||||
|
||||
public NAModule3Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProperties(NAStationModule moduleData) {
|
||||
updateProperties(moduleData.getFirmware(), moduleData.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMeasurements() {
|
||||
List<String> types = Arrays.asList(SUM_RAIN);
|
||||
|
||||
if (isLinked(CHANNEL_SUM_RAIN_THIS_WEEK)) {
|
||||
getMeasurements(getParentId(), getId(), ONE_WEEK, types, Arrays.asList(CHANNEL_SUM_RAIN_THIS_WEEK),
|
||||
channelMeasurements);
|
||||
}
|
||||
|
||||
if (isLinked(CHANNEL_SUM_RAIN_THIS_MONTH)) {
|
||||
getMeasurements(getParentId(), getId(), ONE_MONTH, types, Arrays.asList(CHANNEL_SUM_RAIN_THIS_MONTH),
|
||||
channelMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
|
||||
if (dashboardData != null) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_RAIN:
|
||||
return toQuantityType(dashboardData.getRain(), API_RAIN_UNIT);
|
||||
case CHANNEL_SUM_RAIN1:
|
||||
return toQuantityType(dashboardData.getSumRain1(), API_RAIN_UNIT);
|
||||
case CHANNEL_SUM_RAIN24:
|
||||
return toQuantityType(dashboardData.getSumRain24(), API_RAIN_UNIT);
|
||||
case CHANNEL_TIMEUTC:
|
||||
return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
|
||||
}
|
||||
}
|
||||
|
||||
switch (channelId) {
|
||||
case CHANNEL_SUM_RAIN_THIS_WEEK:
|
||||
case CHANNEL_SUM_RAIN_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_RAIN_UNIT);
|
||||
}
|
||||
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReachable() {
|
||||
boolean result = false;
|
||||
Optional<NAStationModule> module = getModule();
|
||||
if (module.isPresent()) {
|
||||
Boolean reachable = module.get().getReachable();
|
||||
result = reachable != null ? reachable.booleanValue() : false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.station;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.binding.netatmo.internal.WeatherUtils;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
|
||||
|
||||
import io.swagger.client.model.NADashboardData;
|
||||
import io.swagger.client.model.NAStationModule;
|
||||
|
||||
/**
|
||||
* {@link NAModule4Handler} is the class used to handle the additional
|
||||
* indoor module capable of reporting temperature, humidity and CO2 level
|
||||
*
|
||||
* @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 NAModule4Handler extends NetatmoModuleHandler<NAStationModule> {
|
||||
private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
|
||||
|
||||
public NAModule4Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProperties(NAStationModule moduleData) {
|
||||
updateProperties(moduleData.getFirmware(), moduleData.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMeasurements() {
|
||||
updateDayMeasurements();
|
||||
updateWeekMeasurements();
|
||||
updateMonthMeasurements();
|
||||
}
|
||||
|
||||
private void updateDayMeasurements() {
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> types = new ArrayList<>();
|
||||
addMeasurement(channels, types, CHANNEL_MIN_CO2, MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_CO2, MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY, MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY, MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2, DATE_MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2, DATE_MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY, DATE_MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY, DATE_MAX_HUM);
|
||||
if (!channels.isEmpty()) {
|
||||
getMeasurements(getParentId(), getId(), ONE_DAY, types, channels, channelMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWeekMeasurements() {
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> types = new ArrayList<>();
|
||||
addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_WEEK, MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_WEEK, MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_WEEK, MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_WEEK, MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_WEEK, MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_WEEK, MAX_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_WEEK, DATE_MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_WEEK, DATE_MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK, DATE_MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK, DATE_MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_WEEK, DATE_MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_WEEK, DATE_MAX_TEMP);
|
||||
if (!channels.isEmpty()) {
|
||||
getMeasurements(getParentId(), getId(), ONE_WEEK, types, channels, channelMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMonthMeasurements() {
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> types = new ArrayList<>();
|
||||
addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_MONTH, MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_MONTH, MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_MONTH, MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_MONTH, MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_MONTH, MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_MONTH, MAX_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_MONTH, DATE_MIN_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_MONTH, DATE_MAX_CO2);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH, DATE_MIN_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH, DATE_MAX_HUM);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_MONTH, DATE_MIN_TEMP);
|
||||
addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_MONTH, DATE_MAX_TEMP);
|
||||
if (!channels.isEmpty()) {
|
||||
getMeasurements(getParentId(), getId(), ONE_MONTH, types, channels, channelMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
|
||||
if (dashboardData != null) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_TEMP_TREND:
|
||||
return toStringType(dashboardData.getTempTrend());
|
||||
case CHANNEL_CO2:
|
||||
return toQuantityType(dashboardData.getCO2(), API_CO2_UNIT);
|
||||
case CHANNEL_TEMPERATURE:
|
||||
return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DATE_MIN_TEMP:
|
||||
return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_DATE_MAX_TEMP:
|
||||
return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_MIN_TEMP:
|
||||
return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_MAX_TEMP:
|
||||
return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_TIMEUTC:
|
||||
return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
|
||||
case CHANNEL_HUMIDITY:
|
||||
return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
|
||||
case CHANNEL_HUMIDEX:
|
||||
return toDecimalType(
|
||||
WeatherUtils.getHumidex(dashboardData.getTemperature(), dashboardData.getHumidity()));
|
||||
case CHANNEL_HEATINDEX:
|
||||
return toQuantityType(
|
||||
WeatherUtils.getHeatIndex(dashboardData.getTemperature(), dashboardData.getHumidity()),
|
||||
API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DEWPOINT:
|
||||
return toQuantityType(
|
||||
WeatherUtils.getDewPoint(dashboardData.getTemperature(), dashboardData.getHumidity()),
|
||||
API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DEWPOINTDEP:
|
||||
Double dewpoint = WeatherUtils.getDewPoint(dashboardData.getTemperature(),
|
||||
dashboardData.getHumidity());
|
||||
return toQuantityType(WeatherUtils.getDewPointDep(dashboardData.getTemperature(), dewpoint),
|
||||
API_TEMPERATURE_UNIT);
|
||||
}
|
||||
}
|
||||
|
||||
switch (channelId) {
|
||||
case CHANNEL_MIN_CO2:
|
||||
case CHANNEL_MIN_CO2_THIS_WEEK:
|
||||
case CHANNEL_MIN_CO2_THIS_MONTH:
|
||||
case CHANNEL_MAX_CO2:
|
||||
case CHANNEL_MAX_CO2_THIS_WEEK:
|
||||
case CHANNEL_MAX_CO2_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_CO2_UNIT);
|
||||
case CHANNEL_MIN_HUMIDITY:
|
||||
case CHANNEL_MIN_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_MIN_HUMIDITY_THIS_MONTH:
|
||||
case CHANNEL_MAX_HUMIDITY:
|
||||
case CHANNEL_MAX_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_MAX_HUMIDITY_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_HUMIDITY_UNIT);
|
||||
case CHANNEL_MIN_TEMP_THIS_WEEK:
|
||||
case CHANNEL_MIN_TEMP_THIS_MONTH:
|
||||
case CHANNEL_MAX_TEMP_THIS_WEEK:
|
||||
case CHANNEL_MAX_TEMP_THIS_MONTH:
|
||||
return toQuantityType(channelMeasurements.get(channelId), API_TEMPERATURE_UNIT);
|
||||
case CHANNEL_DATE_MIN_CO2:
|
||||
case CHANNEL_DATE_MIN_CO2_THIS_WEEK:
|
||||
case CHANNEL_DATE_MIN_CO2_THIS_MONTH:
|
||||
case CHANNEL_DATE_MAX_CO2:
|
||||
case CHANNEL_DATE_MAX_CO2_THIS_WEEK:
|
||||
case CHANNEL_DATE_MAX_CO2_THIS_MONTH:
|
||||
case CHANNEL_DATE_MIN_HUMIDITY:
|
||||
case CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH:
|
||||
case CHANNEL_DATE_MAX_HUMIDITY:
|
||||
case CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK:
|
||||
case CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH:
|
||||
case CHANNEL_DATE_MIN_TEMP_THIS_WEEK:
|
||||
case CHANNEL_DATE_MIN_TEMP_THIS_MONTH:
|
||||
case CHANNEL_DATE_MAX_TEMP_THIS_WEEK:
|
||||
case CHANNEL_DATE_MAX_TEMP_THIS_MONTH:
|
||||
return toDateTimeType(channelMeasurements.get(channelId), timeZoneProvider.getTimeZone());
|
||||
}
|
||||
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReachable() {
|
||||
boolean result = false;
|
||||
Optional<NAStationModule> module = getModule();
|
||||
if (module.isPresent()) {
|
||||
Boolean reachable = module.get().getReachable();
|
||||
result = reachable != null ? reachable.booleanValue() : false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.thermostat;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
|
||||
|
||||
import io.swagger.client.model.NAPlug;
|
||||
import io.swagger.client.model.NAYearMonth;
|
||||
|
||||
/**
|
||||
* {@link NAPlugHandler} is the class used to handle the plug
|
||||
* device of a thermostat set
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution OH2 version
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAPlugHandler extends NetatmoDeviceHandler<NAPlug> {
|
||||
|
||||
public NAPlugHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<NAPlug> updateReadings() {
|
||||
Optional<NAPlug> result = getBridgeHandler().flatMap(handler -> handler.getThermostatsDataBody(getId()))
|
||||
.map(dataBody -> dataBody.getDevices().stream()
|
||||
.filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
|
||||
result.ifPresent(device -> {
|
||||
device.getModules().forEach(child -> childs.put(child.getId(), child));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProperties(NAPlug deviceData) {
|
||||
updateProperties(deviceData.getFirmware(), deviceData.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_CONNECTED_BOILER:
|
||||
return getDevice().map(d -> toOnOffType(d.getPlugConnectedBoiler())).orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_LAST_PLUG_SEEN:
|
||||
return getDevice().map(d -> toDateTimeType(d.getLastPlugSeen(), timeZoneProvider.getTimeZone()))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_LAST_BILAN:
|
||||
return toDateTimeType(getLastBilan());
|
||||
}
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
public @Nullable ZonedDateTime getLastBilan() {
|
||||
Optional<NAYearMonth> lastBilan = getDevice().map(d -> d.getLastBilan());
|
||||
if (lastBilan.isPresent()) {
|
||||
ZonedDateTime zonedDT = ZonedDateTime.of(lastBilan.get().getY(), lastBilan.get().getM(), 1, 0, 0, 0, 0,
|
||||
ZonedDateTime.now().getZone());
|
||||
return zonedDT.with(TemporalAdjusters.lastDayOfMonth());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> getDataTimestamp() {
|
||||
return getDevice().map(d -> d.getLastStatusStore());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.thermostat;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.openhab.binding.netatmo.internal.NATherm1StateDescriptionProvider;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.swagger.client.api.ThermostatApi;
|
||||
import io.swagger.client.model.NAMeasureResponse;
|
||||
import io.swagger.client.model.NASetpoint;
|
||||
import io.swagger.client.model.NAThermProgram;
|
||||
import io.swagger.client.model.NAThermostat;
|
||||
import io.swagger.client.model.NATimeTableItem;
|
||||
import io.swagger.client.model.NAZone;
|
||||
|
||||
/**
|
||||
* {@link NATherm1Handler} is the class used to handle the thermostat
|
||||
* module of a thermostat set
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution OH2 version
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NATherm1Handler extends NetatmoModuleHandler<NAThermostat> {
|
||||
private final Logger logger = LoggerFactory.getLogger(NATherm1Handler.class);
|
||||
private final NATherm1StateDescriptionProvider stateDescriptionProvider;
|
||||
|
||||
public NATherm1Handler(Thing thing, NATherm1StateDescriptionProvider stateDescriptionProvider,
|
||||
final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateProperties(NAThermostat moduleData) {
|
||||
updateProperties(moduleData.getFirmware(), moduleData.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChannels(Object moduleObject) {
|
||||
if (isRefreshRequired()) {
|
||||
measurableChannels.getAsCsv().ifPresent(csvParams -> {
|
||||
getApi().ifPresent(api -> {
|
||||
NAMeasureResponse measures = api.getmeasure(getParentId(), "max", csvParams, getId(), null, null, 1,
|
||||
true, true);
|
||||
measurableChannels.setMeasures(measures);
|
||||
});
|
||||
});
|
||||
setRefreshRequired(false);
|
||||
}
|
||||
super.updateChannels(moduleObject);
|
||||
|
||||
getModule().ifPresent(this::updateStateDescription);
|
||||
}
|
||||
|
||||
private void updateStateDescription(NAThermostat thermostat) {
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
for (NAThermProgram planning : thermostat.getThermProgramList()) {
|
||||
options.add(new StateOption(planning.getProgramId(), planning.getName()));
|
||||
}
|
||||
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_PLANNING), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
Optional<NAThermostat> thermostat = getModule();
|
||||
switch (channelId) {
|
||||
case CHANNEL_THERM_ORIENTATION:
|
||||
return thermostat.map(m -> toDecimalType(m.getThermOrientation())).orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_THERM_RELAY:
|
||||
return thermostat.map(m -> m.getThermRelayCmd() == 100 ? (State) OnOffType.ON : (State) OnOffType.OFF)
|
||||
.orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_TEMPERATURE:
|
||||
return thermostat.map(m -> toQuantityType(m.getMeasured().getTemperature(), API_TEMPERATURE_UNIT))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_SETPOINT_TEMP:
|
||||
return getCurrentSetpoint();
|
||||
case CHANNEL_TIMEUTC:
|
||||
return thermostat.map(m -> toDateTimeType(m.getMeasured().getTime(), timeZoneProvider.getTimeZone()))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_SETPOINT_END_TIME: {
|
||||
if (thermostat.isPresent()) {
|
||||
NASetpoint setpoint = thermostat.get().getSetpoint();
|
||||
if (setpoint != null) {
|
||||
Integer endTime = setpoint.getSetpointEndtime();
|
||||
if (endTime == null) {
|
||||
endTime = getNextProgramTime(thermostat.get().getThermProgramList());
|
||||
}
|
||||
return toDateTimeType(endTime, timeZoneProvider.getTimeZone());
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
case CHANNEL_SETPOINT_MODE:
|
||||
return getSetpoint();
|
||||
case CHANNEL_PLANNING: {
|
||||
String currentPlanning = "-";
|
||||
if (thermostat.isPresent()) {
|
||||
for (NAThermProgram program : thermostat.get().getThermProgramList()) {
|
||||
if (program.getSelected() == Boolean.TRUE) {
|
||||
currentPlanning = program.getProgramId();
|
||||
}
|
||||
}
|
||||
return toStringType(currentPlanning);
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
private State getSetpoint() {
|
||||
return getModule()
|
||||
.map(m -> m.getSetpoint() != null ? toStringType(m.getSetpoint().getSetpointMode()) : UnDefType.NULL)
|
||||
.orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
private State getCurrentSetpoint() {
|
||||
Optional<NAThermostat> thermostat = getModule();
|
||||
if (thermostat.isPresent()) {
|
||||
NASetpoint setPoint = thermostat.get().getSetpoint();
|
||||
if (setPoint != null) {
|
||||
String currentMode = setPoint.getSetpointMode();
|
||||
|
||||
NAThermProgram currentProgram = thermostat.get().getThermProgramList().stream()
|
||||
.filter(p -> p.getSelected() != null && p.getSelected()).findFirst().get();
|
||||
switch (currentMode) {
|
||||
case CHANNEL_SETPOINT_MODE_MANUAL:
|
||||
return toDecimalType(setPoint.getSetpointTemp());
|
||||
case CHANNEL_SETPOINT_MODE_AWAY:
|
||||
NAZone zone = getZone(currentProgram.getZones(), 2);
|
||||
return toDecimalType(zone.getTemp());
|
||||
case CHANNEL_SETPOINT_MODE_HG:
|
||||
NAZone zone1 = getZone(currentProgram.getZones(), 3);
|
||||
return toDecimalType(zone1.getTemp());
|
||||
case CHANNEL_SETPOINT_MODE_PROGRAM:
|
||||
NATimeTableItem currentProgramMode = getCurrentProgramMode(
|
||||
thermostat.get().getThermProgramList());
|
||||
if (currentProgramMode != null) {
|
||||
NAZone zone2 = getZone(currentProgram.getZones(), currentProgramMode.getId());
|
||||
return toDecimalType(zone2.getTemp());
|
||||
}
|
||||
case CHANNEL_SETPOINT_MODE_OFF:
|
||||
case CHANNEL_SETPOINT_MODE_MAX:
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
private NAZone getZone(List<NAZone> zones, int searchedId) {
|
||||
return zones.stream().filter(z -> z.getId() == searchedId).findFirst().get();
|
||||
}
|
||||
|
||||
private long getNetatmoProgramBaseTime() {
|
||||
Calendar mondayZero = Calendar.getInstance();
|
||||
mondayZero.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
|
||||
mondayZero.set(Calendar.HOUR_OF_DAY, 0);
|
||||
mondayZero.set(Calendar.MINUTE, 0);
|
||||
mondayZero.set(Calendar.SECOND, 0);
|
||||
return mondayZero.getTimeInMillis();
|
||||
}
|
||||
|
||||
private @Nullable NATimeTableItem getCurrentProgramMode(List<NAThermProgram> thermProgramList) {
|
||||
NATimeTableItem lastProgram = null;
|
||||
Calendar now = Calendar.getInstance();
|
||||
long diff = (now.getTimeInMillis() - getNetatmoProgramBaseTime()) / 1000 / 60;
|
||||
|
||||
Optional<NAThermProgram> currentProgram = thermProgramList.stream()
|
||||
.filter(p -> p.getSelected() != null && p.getSelected()).findFirst();
|
||||
|
||||
if (currentProgram.isPresent()) {
|
||||
Stream<NATimeTableItem> pastPrograms = currentProgram.get().getTimetable().stream()
|
||||
.filter(t -> t.getMOffset() < diff);
|
||||
Optional<NATimeTableItem> program = pastPrograms.reduce((first, second) -> second);
|
||||
if (program.isPresent()) {
|
||||
lastProgram = program.get();
|
||||
}
|
||||
}
|
||||
|
||||
return lastProgram;
|
||||
}
|
||||
|
||||
private int getNextProgramTime(List<NAThermProgram> thermProgramList) {
|
||||
Calendar now = Calendar.getInstance();
|
||||
long diff = (now.getTimeInMillis() - getNetatmoProgramBaseTime()) / 1000 / 60;
|
||||
|
||||
int result = -1;
|
||||
|
||||
for (NAThermProgram thermProgram : thermProgramList) {
|
||||
if (thermProgram.getSelected() != null && thermProgram.getSelected()) {
|
||||
// By default we'll use the first slot of next week - this case will be true if
|
||||
// we are in the last schedule of the week so below loop will not exit by break
|
||||
int next = thermProgram.getTimetable().get(0).getMOffset() + (7 * 24 * 60);
|
||||
|
||||
for (NATimeTableItem timeTable : thermProgram.getTimetable()) {
|
||||
if (timeTable.getMOffset() > diff) {
|
||||
next = timeTable.getMOffset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result = (int) (next * 60 + (getNetatmoProgramBaseTime() / 1000));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
super.handleCommand(channelUID, command);
|
||||
if (!(command instanceof RefreshType)) {
|
||||
try {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_SETPOINT_MODE: {
|
||||
String targetMode = command.toString();
|
||||
if (CHANNEL_SETPOINT_MODE_MANUAL.equals(targetMode)) {
|
||||
logger.info(
|
||||
"Switching to manual mode is done by assigning a setpoint temperature - command dropped");
|
||||
updateState(channelUID, getSetpoint());
|
||||
} else {
|
||||
pushSetpointUpdate(targetMode, null, null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHANNEL_SETPOINT_TEMP: {
|
||||
BigDecimal spTemp = null;
|
||||
if (command instanceof QuantityType) {
|
||||
@SuppressWarnings("unchecked")
|
||||
QuantityType<Temperature> quantity = ((QuantityType<Temperature>) command)
|
||||
.toUnit(API_TEMPERATURE_UNIT);
|
||||
if (quantity != null) {
|
||||
spTemp = quantity.toBigDecimal().setScale(1, RoundingMode.HALF_UP);
|
||||
}
|
||||
} else {
|
||||
spTemp = new BigDecimal(command.toString()).setScale(1, RoundingMode.HALF_UP);
|
||||
}
|
||||
if (spTemp != null) {
|
||||
pushSetpointUpdate(CHANNEL_SETPOINT_MODE_MANUAL, getSetpointEndTime(), spTemp.floatValue());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case CHANNEL_PLANNING: {
|
||||
getApi().ifPresent(api -> {
|
||||
api.switchschedule(getParentId(), getId(), command.toString());
|
||||
updateState(channelUID, new StringType(command.toString()));
|
||||
invalidateParentCacheAndRefresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pushSetpointUpdate(String target_mode, @Nullable Integer setpointEndtime,
|
||||
@Nullable Float setpointTemp) {
|
||||
getApi().ifPresent(api -> {
|
||||
api.setthermpoint(getParentId(), getId(), target_mode, setpointEndtime, setpointTemp);
|
||||
invalidateParentCacheAndRefresh();
|
||||
});
|
||||
}
|
||||
|
||||
private int getSetpointEndTime() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.MINUTE, getSetPointDefaultDuration());
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(Calendar.MILLISECOND, 0);
|
||||
return (int) (cal.getTimeInMillis() / 1000);
|
||||
}
|
||||
|
||||
private int getSetPointDefaultDuration() {
|
||||
// TODO : this informations could be sourced from Netatmo API instead of a local configuration element
|
||||
Configuration conf = config;
|
||||
Object defaultDuration = conf != null ? conf.get(SETPOINT_DEFAULT_DURATION) : null;
|
||||
if (defaultDuration instanceof BigDecimal) {
|
||||
return ((BigDecimal) defaultDuration).intValue();
|
||||
}
|
||||
return 60;
|
||||
}
|
||||
|
||||
private Optional<ThermostatApi> getApi() {
|
||||
return getBridgeHandler().flatMap(handler -> handler.getThermostatApi());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.webhook;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link NAWebhookCameraEvent} is responsible to hold
|
||||
* data given back by the Netatmo API when calling the webhook
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class NAWebhookCameraEvent {
|
||||
|
||||
public enum AppTypeEnum {
|
||||
@SerializedName("app_camera")
|
||||
CAMERA("camera");
|
||||
|
||||
private String value;
|
||||
|
||||
AppTypeEnum(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@SerializedName("app_type")
|
||||
private AppTypeEnum appType = null;
|
||||
|
||||
public AppTypeEnum getAppType() {
|
||||
return appType;
|
||||
}
|
||||
|
||||
public enum EventTypeEnum {
|
||||
@SerializedName("person")
|
||||
PERSON("person"),
|
||||
|
||||
@SerializedName("person_away")
|
||||
PERSON_AWAY("person_away"),
|
||||
|
||||
@SerializedName("movement")
|
||||
MOVEMENT("movement"),
|
||||
|
||||
@SerializedName("outdoor")
|
||||
OUTDOOR("outdoor"),
|
||||
|
||||
@SerializedName("connection")
|
||||
CONNECTION("connection"),
|
||||
|
||||
@SerializedName("disconnection")
|
||||
DISCONNECTION("disconnection"),
|
||||
|
||||
@SerializedName("on")
|
||||
ON("on"),
|
||||
|
||||
@SerializedName("off")
|
||||
OFF("off"),
|
||||
|
||||
@SerializedName("boot")
|
||||
BOOT("boot"),
|
||||
|
||||
@SerializedName("sd")
|
||||
SD("sd"),
|
||||
|
||||
@SerializedName("alim")
|
||||
ALIM("alim"),
|
||||
|
||||
@SerializedName("daily_summary")
|
||||
DAILY_SUMMARY("daily_summary"),
|
||||
|
||||
@SerializedName("new_module")
|
||||
NEW_MODULE("new_module"),
|
||||
|
||||
@SerializedName("module_connect")
|
||||
MODULE_CONNECT("module_connect"),
|
||||
|
||||
@SerializedName("module_disconnect")
|
||||
MODULE_DISCONNECT("module_disconnect"),
|
||||
|
||||
@SerializedName("module_low_battery")
|
||||
MODULE_LOW_BATTERY("module_low_battery"),
|
||||
|
||||
@SerializedName("module_end_update")
|
||||
MODULE_END_UPDATE("module_end_update"),
|
||||
|
||||
@SerializedName("tag_big_move")
|
||||
TAG_BIG_MOVE("tag_big_move"),
|
||||
|
||||
@SerializedName("tag_small_move")
|
||||
TAG_SMALL_MOVE("tag_small_move"),
|
||||
|
||||
@SerializedName("tag_uninstalled")
|
||||
TAG_UNINSTALLED("tag_uninstalled"),
|
||||
|
||||
@SerializedName("tag_open")
|
||||
TAG_OPEN("tag_open");
|
||||
|
||||
private String value;
|
||||
|
||||
EventTypeEnum(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@SerializedName("event_type")
|
||||
private EventTypeEnum eventType = null;
|
||||
|
||||
public EventTypeEnum getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
@SerializedName("camera_id")
|
||||
String cameraId;
|
||||
|
||||
public String getCameraId() {
|
||||
return cameraId;
|
||||
}
|
||||
|
||||
@SerializedName("home_id")
|
||||
String homeId;
|
||||
|
||||
public String getHomeId() {
|
||||
return homeId;
|
||||
}
|
||||
|
||||
@SerializedName("persons")
|
||||
private List<NAWebhookCameraEventPerson> persons = new ArrayList<>();
|
||||
|
||||
public List<NAWebhookCameraEventPerson> getPersons() {
|
||||
return persons;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.webhook;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link NAWebhookCameraEventPerson} is responsible to hold
|
||||
* data given back by the Netatmo API when calling the webhook
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class NAWebhookCameraEventPerson {
|
||||
@SerializedName("id")
|
||||
String id;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@SerializedName("face_id")
|
||||
String faceId;
|
||||
|
||||
public String getFaceId() {
|
||||
return faceId;
|
||||
}
|
||||
|
||||
@SerializedName("face_key")
|
||||
String faceKey;
|
||||
|
||||
public String getFaceKey() {
|
||||
return faceKey;
|
||||
}
|
||||
|
||||
@SerializedName("is_known")
|
||||
Boolean isKnown;
|
||||
|
||||
public Boolean isKnown() {
|
||||
return isKnown;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.webhook;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Scanner;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.osgi.service.http.NamespaceException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Main OSGi service and HTTP servlet for Netatmo Welcome Webhook.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WelcomeWebHookServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1288539782077957954L;
|
||||
private static final String PATH = "/netatmo/%s/camera";
|
||||
private static final String APPLICATION_JSON = "application/json";
|
||||
private static final String CHARSET = "utf-8";
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WelcomeWebHookServlet.class);
|
||||
|
||||
private HttpService httpService;
|
||||
private @Nullable NetatmoBridgeHandler bridgeHandler;
|
||||
private String path;
|
||||
|
||||
public WelcomeWebHookServlet(HttpService httpService, String id) {
|
||||
this.httpService = httpService;
|
||||
this.path = String.format(PATH, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* OSGi activation callback.
|
||||
*
|
||||
* @param config Service config.
|
||||
*/
|
||||
public void activate(NetatmoBridgeHandler bridgeHandler) {
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
try {
|
||||
httpService.registerServlet(path, this, null, httpService.createDefaultHttpContext());
|
||||
logger.debug("Started Netatmo Webhook servlet at {}", path);
|
||||
} catch (ServletException | NamespaceException e) {
|
||||
logger.error("Could not start Netatmo Webhook servlet: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OSGi deactivation callback.
|
||||
*/
|
||||
public void deactivate() {
|
||||
httpService.unregister(path);
|
||||
logger.debug("Netatmo webhook servlet stopped");
|
||||
this.bridgeHandler = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
if (req == null || resp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String data = inputStreamToString(req);
|
||||
NetatmoBridgeHandler handler = bridgeHandler;
|
||||
if (!data.isEmpty() && handler != null) {
|
||||
NAWebhookCameraEvent event = gson.fromJson(data, NAWebhookCameraEvent.class);
|
||||
logger.debug("Event transmitted from restService");
|
||||
handler.webHookEvent(event);
|
||||
}
|
||||
|
||||
setHeaders(resp);
|
||||
resp.getWriter().write("");
|
||||
}
|
||||
|
||||
private String inputStreamToString(HttpServletRequest req) throws IOException {
|
||||
String value = "";
|
||||
try (Scanner scanner = new Scanner(req.getInputStream())) {
|
||||
scanner.useDelimiter("\\A");
|
||||
value = scanner.hasNext() ? scanner.next() : "";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void setHeaders(HttpServletResponse response) {
|
||||
response.setCharacterEncoding(CHARSET);
|
||||
response.setContentType(APPLICATION_JSON);
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
response.setHeader("Access-Control-Allow-Methods", "POST");
|
||||
response.setHeader("Access-Control-Max-Age", "3600");
|
||||
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.welcome;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.binding.netatmo.internal.camera.CameraHandler;
|
||||
|
||||
/**
|
||||
* {@link NAWelcomeCameraHandler} is the class used to handle the Welcome Camera Data
|
||||
*
|
||||
* @author Ing. Peter Weiss - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAWelcomeCameraHandler extends CameraHandler {
|
||||
|
||||
public NAWelcomeCameraHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_WELCOME_CAMERA_STATUS:
|
||||
return getStatusState();
|
||||
case CHANNEL_WELCOME_CAMERA_SDSTATUS:
|
||||
return getSdStatusState();
|
||||
case CHANNEL_WELCOME_CAMERA_ALIMSTATUS:
|
||||
return getAlimStatusState();
|
||||
case CHANNEL_WELCOME_CAMERA_ISLOCAL:
|
||||
return getIsLocalState();
|
||||
case CHANNEL_WELCOME_CAMERA_LIVEPICTURE_URL:
|
||||
return getLivePictureURLState();
|
||||
case CHANNEL_WELCOME_CAMERA_LIVEPICTURE:
|
||||
return getLivePictureState();
|
||||
case CHANNEL_WELCOME_CAMERA_LIVESTREAM_URL:
|
||||
return getLiveStreamState();
|
||||
}
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.welcome;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
|
||||
import org.openhab.binding.netatmo.internal.camera.CameraHandler;
|
||||
import org.openhab.binding.netatmo.internal.handler.AbstractNetatmoThingHandler;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
|
||||
import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.swagger.client.model.NAWelcomeEvent;
|
||||
import io.swagger.client.model.NAWelcomeHome;
|
||||
import io.swagger.client.model.NAWelcomePlace;
|
||||
import io.swagger.client.model.NAWelcomeSnapshot;
|
||||
import io.swagger.client.model.NAWelcomeSubEvent;
|
||||
|
||||
/**
|
||||
* {@link NAWelcomeHomeHandler} is the class used to handle the Welcome Home Data
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
* @author Ing. Peter Weiss - Welcome camera implementation
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAWelcomeHomeHandler extends NetatmoDeviceHandler<NAWelcomeHome> {
|
||||
private final Logger logger = LoggerFactory.getLogger(NAWelcomeHomeHandler.class);
|
||||
|
||||
private int iPersons = -1;
|
||||
private int iUnknowns = -1;
|
||||
private @Nullable NAWelcomeEvent lastEvent;
|
||||
private boolean isNewLastEvent;
|
||||
private @Nullable Integer dataTimeStamp;
|
||||
|
||||
public NAWelcomeHomeHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<NAWelcomeHome> updateReadings() {
|
||||
Optional<NAWelcomeHome> result = getBridgeHandler().flatMap(handler -> handler.getWelcomeDataBody(getId()))
|
||||
.map(dataBody -> dataBody.getHomes().stream().filter(device -> device.getId().equalsIgnoreCase(getId()))
|
||||
.findFirst().orElse(null));
|
||||
// data time stamp is updated to now as WelcomeDataBody does not provide any information according to this need
|
||||
dataTimeStamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
|
||||
result.ifPresent(home -> {
|
||||
home.getCameras().forEach(camera -> childs.put(camera.getId(), camera));
|
||||
|
||||
// Check how many persons are at home
|
||||
iPersons = 0;
|
||||
iUnknowns = 0;
|
||||
|
||||
logger.debug("welcome home '{}' calculate Persons at home count", getId());
|
||||
home.getPersons().forEach(person -> {
|
||||
iPersons += person.getOutOfSight() ? 0 : 1;
|
||||
if (person.getPseudo() != null) {
|
||||
childs.put(person.getId(), person);
|
||||
} else {
|
||||
iUnknowns += person.getOutOfSight() ? 0 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
NAWelcomeEvent previousLastEvent = lastEvent;
|
||||
lastEvent = home.getEvents().stream().max(Comparator.comparingInt(NAWelcomeEvent::getTime)).orElse(null);
|
||||
isNewLastEvent = previousLastEvent != null && !previousLastEvent.equals(lastEvent);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
Optional<NAWelcomeEvent> lastEvt = getLastEvent();
|
||||
switch (channelId) {
|
||||
case CHANNEL_WELCOME_HOME_CITY:
|
||||
return getPlaceInfo(NAWelcomePlace::getCity);
|
||||
case CHANNEL_WELCOME_HOME_COUNTRY:
|
||||
return getPlaceInfo(NAWelcomePlace::getCountry);
|
||||
case CHANNEL_WELCOME_HOME_TIMEZONE:
|
||||
return getPlaceInfo(NAWelcomePlace::getTimezone);
|
||||
case CHANNEL_WELCOME_HOME_PERSONCOUNT:
|
||||
return iPersons != -1 ? new DecimalType(iPersons) : UnDefType.UNDEF;
|
||||
case CHANNEL_WELCOME_HOME_UNKNOWNCOUNT:
|
||||
return iUnknowns != -1 ? new DecimalType(iUnknowns) : UnDefType.UNDEF;
|
||||
case CHANNEL_WELCOME_EVENT_TYPE:
|
||||
return lastEvt.map(e -> toStringType(e.getType())).orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_WELCOME_EVENT_TIME:
|
||||
return lastEvt.map(e -> toDateTimeType(e.getTime(), timeZoneProvider.getTimeZone()))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_WELCOME_EVENT_CAMERAID:
|
||||
if (lastEvt.isPresent()) {
|
||||
return findNAThing(lastEvt.get().getCameraId()).map(c -> toStringType(c.getThing().getLabel()))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
} else {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
case CHANNEL_WELCOME_EVENT_PERSONID:
|
||||
if (lastEvt.isPresent()) {
|
||||
return findNAThing(lastEvt.get().getPersonId()).map(p -> toStringType(p.getThing().getLabel()))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
} else {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
case CHANNEL_WELCOME_EVENT_SNAPSHOT:
|
||||
return findSnapshotURL().map(url -> toRawType(url)).orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_WELCOME_EVENT_SNAPSHOT_URL:
|
||||
return findSnapshotURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_WELCOME_EVENT_VIDEO_URL:
|
||||
if (lastEvt.isPresent() && lastEvt.get().getVideoId() != null) {
|
||||
String cameraId = lastEvt.get().getCameraId();
|
||||
Optional<AbstractNetatmoThingHandler> thing = findNAThing(cameraId);
|
||||
if (thing.isPresent()) {
|
||||
CameraHandler eventCamera = (CameraHandler) thing.get();
|
||||
Optional<String> streamUrl = eventCamera.getStreamURL(lastEvt.get().getVideoId());
|
||||
if (streamUrl.isPresent()) {
|
||||
return new StringType(streamUrl.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
case CHANNEL_WELCOME_EVENT_VIDEOSTATUS:
|
||||
return lastEvt.map(e -> e.getVideoId() != null ? toStringType(e.getVideoStatus()) : UnDefType.UNDEF)
|
||||
.orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_WELCOME_EVENT_ISARRIVAL:
|
||||
return lastEvt.map(e -> toOnOffType(e.getIsArrival())).orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_WELCOME_EVENT_MESSAGE:
|
||||
return findEventMessage().map(m -> (State) new StringType(m.replace("<b>", "").replace("</b>", "")))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_WELCOME_EVENT_SUBTYPE:
|
||||
return lastEvt.map(e -> toDecimalType(e.getSubType())).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void triggerChannelIfRequired(String channelId) {
|
||||
if (isNewLastEvent) {
|
||||
if (CHANNEL_CAMERA_EVENT.equals(channelId)) {
|
||||
findDetectedObjectTypes(getLastEvent())
|
||||
.forEach(detectedType -> triggerChannel(channelId, detectedType));
|
||||
}
|
||||
}
|
||||
super.triggerChannelIfRequired(channelId);
|
||||
}
|
||||
|
||||
private static Set<String> findDetectedObjectTypes(Optional<NAWelcomeEvent> eventOptional) {
|
||||
Set<String> detectedObjectTypes = new TreeSet<>();
|
||||
if (!eventOptional.isPresent()) {
|
||||
return detectedObjectTypes;
|
||||
}
|
||||
|
||||
NAWelcomeEvent event = eventOptional.get();
|
||||
|
||||
if (NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.toString().equals(event.getType())) {
|
||||
if (event.getPersonId() != null) {
|
||||
detectedObjectTypes.add(NAWelcomeSubEvent.TypeEnum.HUMAN.name());
|
||||
} else {
|
||||
Optional<NAWelcomeSubEvent.TypeEnum> detectedCategory = findDetectedCategory(event);
|
||||
if (detectedCategory.isPresent()) {
|
||||
detectedObjectTypes.add(detectedCategory.get().name());
|
||||
} else {
|
||||
detectedObjectTypes.add(NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event.getEventList().forEach(subEvent -> {
|
||||
String detectedObjectType = subEvent.getType().name();
|
||||
detectedObjectTypes.add(detectedObjectType);
|
||||
});
|
||||
return detectedObjectTypes;
|
||||
}
|
||||
|
||||
private static Optional<NAWelcomeSubEvent.TypeEnum> findDetectedCategory(NAWelcomeEvent event) {
|
||||
NAWelcomeEvent.CategoryEnum category = event.getCategory();
|
||||
if (category != null) {
|
||||
// It is safe to convert the enum, both enums have the same values.
|
||||
return Optional.of(NAWelcomeSubEvent.TypeEnum.valueOf(category.name()));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<String> findEventMessage() {
|
||||
Optional<NAWelcomeEvent> lastEvt = getLastEvent();
|
||||
if (lastEvt.isPresent()) {
|
||||
@Nullable
|
||||
String message = lastEvt.get().getMessage();
|
||||
if (message != null) {
|
||||
return Optional.of(message);
|
||||
}
|
||||
|
||||
return lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getMessage);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Url of the picture
|
||||
*
|
||||
* @return Url of the picture or null
|
||||
*/
|
||||
protected Optional<String> findSnapshotURL() {
|
||||
Optional<NAWelcomeEvent> lastEvt = getLastEvent();
|
||||
if (lastEvt.isPresent()) {
|
||||
@Nullable
|
||||
NAWelcomeSnapshot snapshot = lastEvt.get().getSnapshot();
|
||||
if (snapshot == null) {
|
||||
snapshot = lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getSnapshot).orElse(null);
|
||||
}
|
||||
|
||||
if (snapshot != null && snapshot.getId() != null && snapshot.getKey() != null) {
|
||||
return Optional.of(WELCOME_PICTURE_URL + "?" + WELCOME_PICTURE_IMAGEID + "=" + snapshot.getId() + "&"
|
||||
+ WELCOME_PICTURE_KEY + "=" + snapshot.getKey());
|
||||
} else {
|
||||
logger.debug("Unable to build snapshot url for Home : {}", getId());
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> getDataTimestamp() {
|
||||
Integer timestamp = dataTimeStamp;
|
||||
return timestamp != null ? Optional.of(timestamp) : Optional.empty();
|
||||
}
|
||||
|
||||
private State getPlaceInfo(Function<NAWelcomePlace, String> infoGetFunction) {
|
||||
return getDevice().map(d -> toStringType(infoGetFunction.apply(d.getPlace()))).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
private Optional<NAWelcomeSubEvent> findFirstSubEvent(NAWelcomeEvent event) {
|
||||
return Optional.ofNullable(event).map(NAWelcomeEvent::getEventList)
|
||||
.flatMap(subEvents -> subEvents.stream().findFirst());
|
||||
}
|
||||
|
||||
private Optional<NAWelcomeEvent> getLastEvent() {
|
||||
NAWelcomeEvent evt = lastEvent;
|
||||
return evt != null ? Optional.of(evt) : Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.welcome;
|
||||
|
||||
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
|
||||
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
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.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
|
||||
|
||||
import io.swagger.client.api.WelcomeApi;
|
||||
import io.swagger.client.model.NAWelcomeEvent;
|
||||
import io.swagger.client.model.NAWelcomeEventResponse;
|
||||
import io.swagger.client.model.NAWelcomeFace;
|
||||
import io.swagger.client.model.NAWelcomePerson;
|
||||
|
||||
/**
|
||||
* {@link NAWelcomePersonHandler} is the class used to handle the Welcome Home Data
|
||||
*
|
||||
* @author Ing. Peter Weiss - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NAWelcomePersonHandler extends NetatmoModuleHandler<NAWelcomePerson> {
|
||||
private @Nullable String avatarURL;
|
||||
private @Nullable NAWelcomeEvent lastEvent;
|
||||
|
||||
public NAWelcomePersonHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
|
||||
super(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChannels(Object module) {
|
||||
if (isRefreshRequired()) {
|
||||
getApi().ifPresent(api -> {
|
||||
NAWelcomeEventResponse eventResponse = api.getlasteventof(getParentId(), getId(), 10);
|
||||
|
||||
// Search the last event for this person
|
||||
List<NAWelcomeEvent> rawEventList = eventResponse.getBody().getEventsList();
|
||||
rawEventList.forEach(event -> {
|
||||
if (event.getPersonId() != null && event.getPersonId().equalsIgnoreCase(getId())
|
||||
&& (lastEvent == null || lastEvent.getTime() < event.getTime())) {
|
||||
lastEvent = event;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setRefreshRequired(false);
|
||||
}
|
||||
super.updateChannels(module);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State getNAThingProperty(String channelId) {
|
||||
Optional<NAWelcomeEvent> lastEvt = getLastEvent();
|
||||
String url;
|
||||
switch (channelId) {
|
||||
case CHANNEL_WELCOME_PERSON_LASTSEEN:
|
||||
return getModule().map(m -> toDateTimeType(m.getLastSeen(), timeZoneProvider.getTimeZone()))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_WELCOME_PERSON_ATHOME:
|
||||
return getModule()
|
||||
.map(m -> m.getOutOfSight() != null ? toOnOffType(!m.getOutOfSight()) : UnDefType.UNDEF)
|
||||
.orElse(UnDefType.UNDEF);
|
||||
case CHANNEL_WELCOME_PERSON_AVATAR_URL:
|
||||
return toStringType(getAvatarURL());
|
||||
case CHANNEL_WELCOME_PERSON_AVATAR:
|
||||
url = getAvatarURL();
|
||||
return url != null ? toRawType(url) : UnDefType.UNDEF;
|
||||
case CHANNEL_WELCOME_PERSON_LASTMESSAGE:
|
||||
return (lastEvt.isPresent() && lastEvt.get().getMessage() != null)
|
||||
? toStringType(lastEvt.get().getMessage().replace("<b>", "").replace("</b>", ""))
|
||||
: UnDefType.UNDEF;
|
||||
case CHANNEL_WELCOME_PERSON_LASTTIME:
|
||||
return lastEvt.isPresent() ? toDateTimeType(lastEvt.get().getTime(), timeZoneProvider.getTimeZone())
|
||||
: UnDefType.UNDEF;
|
||||
case CHANNEL_WELCOME_PERSON_LASTEVENT:
|
||||
url = getLastEventURL();
|
||||
return url != null ? toRawType(url) : UnDefType.UNDEF;
|
||||
case CHANNEL_WELCOME_PERSON_LASTEVENT_URL:
|
||||
return getLastEventURL() != null ? toStringType(getLastEventURL()) : UnDefType.UNDEF;
|
||||
}
|
||||
return super.getNAThingProperty(channelId);
|
||||
}
|
||||
|
||||
private @Nullable String getLastEventURL() {
|
||||
Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
|
||||
Optional<NAWelcomeEvent> lastEvt = getLastEvent();
|
||||
if (handler.isPresent() && lastEvt.isPresent() && lastEvt.get().getSnapshot() != null) {
|
||||
return handler.get().getPictureUrl(lastEvt.get().getSnapshot().getId(),
|
||||
lastEvt.get().getSnapshot().getKey());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable String getAvatarURL() {
|
||||
Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
|
||||
Optional<NAWelcomePerson> person = getModule();
|
||||
if (handler.isPresent() && avatarURL == null && person.isPresent()) {
|
||||
NAWelcomeFace face = person.get().getFace();
|
||||
if (face != null) {
|
||||
avatarURL = handler.get().getPictureUrl(face.getId(), face.getKey());
|
||||
}
|
||||
}
|
||||
return avatarURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
super.handleCommand(channelUID, command);
|
||||
if ((command instanceof OnOffType) && (CHANNEL_WELCOME_PERSON_ATHOME.equalsIgnoreCase(channelUID.getId()))) {
|
||||
getApi().ifPresent(api -> {
|
||||
if ((OnOffType) command == OnOffType.OFF) {
|
||||
api.setpersonsaway(getParentId(), getId());
|
||||
} else {
|
||||
api.setpersonshome(getParentId(), "[\"" + getId() + "\"]");
|
||||
}
|
||||
invalidateParentCacheAndRefresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<WelcomeApi> getApi() {
|
||||
return getBridgeHandler().flatMap(handler -> handler.getWelcomeApi());
|
||||
}
|
||||
|
||||
private Optional<NAWelcomeEvent> getLastEvent() {
|
||||
NAWelcomeEvent evt = lastEvent;
|
||||
return evt != null ? Optional.of(evt) : Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="netatmo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Netatmo Binding</name>
|
||||
<description>The Netatmo binding integrates Weather Station with companion modules, Healthy Home Coach, Thermostat Plug
|
||||
and Welcome Camera.</description>
|
||||
<author>Gaël L'hopital and Ing. Peter Weiss</author>
|
||||
|
||||
<config-description>
|
||||
<parameter name="backgroundDiscovery" type="boolean">
|
||||
<label>Background Discovery</label>
|
||||
<description>If set to true, the device and its associated modules are updated in the discovery inbox at each API
|
||||
call run to refresh device data. Default is false.</description>
|
||||
<required>false</required>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,173 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
|
||||
https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:netatmo:bridge">
|
||||
<parameter name="clientId" type="text">
|
||||
<label>Client ID</label>
|
||||
<description>Client ID provided for the application you created on http://dev.netatmo.com/createapp</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="clientSecret" type="text">
|
||||
<label>Client Secret</label>
|
||||
<description>Client Secret provided for the application you created</description>
|
||||
<required>true</required>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
|
||||
<parameter name="username" type="text">
|
||||
<label>Username</label>
|
||||
<description>Your Netatmo API username (email)</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="password" type="text">
|
||||
<label>Password</label>
|
||||
<description>Your Netatmo API password</description>
|
||||
<required>true</required>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
|
||||
<parameter name="readStation" type="boolean" required="true">
|
||||
<label>Access Weather Station</label>
|
||||
<description>Read weather station's data.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
|
||||
<parameter name="readHealthyHomeCoach" type="boolean" required="true">
|
||||
<label>Access Healthy Home Coach</label>
|
||||
<description>Read healthy home coach's data.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
|
||||
<parameter name="readThermostat" type="boolean" required="true">
|
||||
<label>Access Thermostat</label>
|
||||
<description>Read and Write thermostat's data.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
|
||||
<parameter name="readWelcome" type="boolean" required="true">
|
||||
<label>Access Welcome Camera</label>
|
||||
<description>Read and Access Welcome camera's data.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
|
||||
<parameter name="readPresence" type="boolean" required="true">
|
||||
<label>Access Presence Camera</label>
|
||||
<description>Read and Access Presence camera's data.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
|
||||
<parameter name="webHookUrl" type="text" required="false">
|
||||
<label>Webhook Address</label>
|
||||
<description>Protocol, public IP and port to access OH2 server from Internet.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="reconnectInterval" type="integer" unit="s" required="true">
|
||||
<label>Reconnect Interval</label>
|
||||
<description>The reconnection interval to Netatmo API (in s).</description>
|
||||
<default>5400</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:netatmo:station">
|
||||
<parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})">
|
||||
<label>Equipment ID</label>
|
||||
<description>Id of the Device (mac address)</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:netatmo:plug">
|
||||
<parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})">
|
||||
<label>Equipment ID</label>
|
||||
<description>Id of the Device (mac address)</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:netatmo:module">
|
||||
<parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})">
|
||||
<label>Module ID</label>
|
||||
<description>Id of the Module</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="parentId" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})">
|
||||
<label>Device ID</label>
|
||||
<description>Id of the main device</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:netatmo:natherm1">
|
||||
<parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})">
|
||||
<label>Module ID</label>
|
||||
<description>Id of the Module</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="parentId" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})">
|
||||
<label>Device ID</label>
|
||||
<description>Id of the main device</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="setpointDefaultDuration" type="integer" required="false">
|
||||
<label>Setpoint Duration</label>
|
||||
<description>Default duration of thermostat change when force to max or manual.</description>
|
||||
<default>60</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:netatmo:welcomehome">
|
||||
<parameter name="id" type="text" required="true">
|
||||
<label>Home ID</label>
|
||||
<description>UUID of the home</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="refreshInterval" type="integer" min="2000" unit="ms" required="false">
|
||||
<label>Refresh Interval</label>
|
||||
<description>The refresh interval to poll Netatmo API (in ms).</description>
|
||||
<default>300000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:netatmo:camera">
|
||||
<parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})">
|
||||
<label>Camera ID</label>
|
||||
<description>Camera MAC Address</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="parentId" type="text">
|
||||
<label>Home ID</label>
|
||||
<description>UUID of the home hosting the camera</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:netatmo:nawelcomeperson">
|
||||
<parameter name="id" type="text">
|
||||
<label>Person ID</label>
|
||||
<description>UUID of the Person</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="parentId" type="text">
|
||||
<label>Home ID</label>
|
||||
<description>UUID of the home</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,334 @@
|
||||
# binding
|
||||
binding.netatmo.name = Netatmo Binding
|
||||
binding.netatmo.description = Dieses Binding integriert die smarte Wetterstation, die Zusatzmodule wie Regenmesser und Windmesser, als auch das smarte Thermostat und den Healthy Home Coach.
|
||||
|
||||
# bridge types
|
||||
thing-type.netatmo.netatmoapi.label = Netatmo API
|
||||
thing-type.netatmo.netatmoapi.description = Netatmo API
|
||||
|
||||
# bridge types config
|
||||
thing-type.config.netatmo.bridge.clientId.label = Client id
|
||||
thing-type.config.netatmo.bridge.clientId.description = Client id der APP, die unter https://dev.netatmo.com/myaccount/createanapp erstellt wurde.
|
||||
|
||||
thing-type.config.netatmo.bridge.clientSecret.label = Client secret
|
||||
thing-type.config.netatmo.bridge.clientSecret.description = Client secret der APP, die unter https://dev.netatmo.com/myaccount/createanapp erstellt wurde.
|
||||
|
||||
thing-type.config.netatmo.bridge.username.label = Benutzer
|
||||
thing-type.config.netatmo.bridge.username.description = Benutzer zur Authentifizierung an der Netatmo API.
|
||||
|
||||
thing-type.config.netatmo.bridge.password.label = Passwort
|
||||
thing-type.config.netatmo.bridge.password.description = Passwort zur Authentifizierung an der Netatmo API.
|
||||
|
||||
thing-type.config.netatmo.bridge.readStation.label = Wetterstation API
|
||||
thing-type.config.netatmo.bridge.readStation.description = Aktiviert den Zugriff auf die Wetterstation API (lesend).
|
||||
|
||||
thing-type.config.netatmo.bridge.readHealthyHomeCoach.label = Healthy Home Coach API
|
||||
thing-type.config.netatmo.bridge.readHealthyHomeCoach.description = Aktiviert den Zugriff auf die Healthy Home Coach API (lesend).
|
||||
|
||||
thing-type.config.netatmo.bridge.readThermostat.label = Thermostat API
|
||||
thing-type.config.netatmo.bridge.readThermostat.description = Aktiviert den Zugriff auf die Thermostat API (lesend und schreibend).
|
||||
|
||||
#thing-type.config.netatmo.bridge.readWelcome.label = Access Welcome camera
|
||||
#thing-type.config.netatmo.bridge.readWelcome.description = Read and Access Welcome camera's data.
|
||||
|
||||
#thing-type.config.netatmo.bridge.readPresence.label = Access Presence camera
|
||||
#thing-type.config.netatmo.bridge.readPresence.description = Read and Access Presence camera's data.
|
||||
|
||||
#thing-type.config.netatmo.bridge.webHookUrl.label = Webhook address
|
||||
#thing-type.config.netatmo.bridge.webHookUrl.description = Protocol, public IP and port to access OH2 server from Internet.
|
||||
|
||||
#thing-type.config.netatmo.bridge.reconnectInterval.label = Reconnect Interval
|
||||
#thing-type.config.netatmo.bridge.reconnectInterval.description = The reconnection interval to Netatmo API (in s).
|
||||
|
||||
# thing types
|
||||
thing-type.netatmo.NAMain.label = Haupt-Indoor-Modul
|
||||
thing-type.netatmo.NAMain.description = Das Haupt-Indoor-Modul liefert Daten wie z.B. Temperatur, Luftdruck, Luftfeuchtigkeit, CO2-Gehalt und Lautstärke.
|
||||
|
||||
thing-type.netatmo.NAModule1.label = Outdoor-Modul
|
||||
thing-type.netatmo.NAModule1.description = Das Outdoor-Modul liefert Daten wie z.B. Temperatur und Luftdruck.
|
||||
|
||||
thing-type.netatmo.NAModule2.label = Windmesser
|
||||
thing-type.netatmo.NAModule2.description = Der Windmesser liefert Daten wie z.B. Windrichtung und Windstärke.
|
||||
|
||||
thing-type.netatmo.NAModule3.label = Regenmesser
|
||||
thing-type.netatmo.NAModule3.description = Der Regenmesser liefert Daten wie z.B. Echtzeit-Messungen zur Intensität des Regens.
|
||||
|
||||
thing-type.netatmo.NAModule4.label = Zusatz-Indoor-Modul
|
||||
thing-type.netatmo.NAModule4.description = Das Zusatz-Indoor-Modul liefert Daten wie z.B. Temperatur, Luftdruck, Luftfeuchtigkeit und CO2-Gehalt.
|
||||
|
||||
thing-type.netatmo.NAPlug.label = Thermostat Relais
|
||||
thing-type.netatmo.NAPlug.description = Das Thermostat Relais liefert Daten des Heizkessels.
|
||||
|
||||
thing-type.netatmo.NATherm1.label = Heizkörperthermostat
|
||||
thing-type.netatmo.NATherm1.description = Das Heizkörperthermostat dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur.
|
||||
|
||||
thing-type.netatmo.NHC.label = Healthy Home Coach
|
||||
thing-type.netatmo.NHC.description = Der Healthy Home Coach liefert Daten wie z.B. Temperatur, Luftdruck, Luftfeuchtigkeit, CO2-Gehalt und Lautstärke.
|
||||
|
||||
#thing-type.netatmo.NAWelcomeHome.label = Welcome Home
|
||||
#thing-type.netatmo.NAWelcomeHome.description = This represents a home hosting a Welcome Camera.
|
||||
|
||||
#thing-type.netatmo.NACamera.label = Welcome Camera
|
||||
#thing-type.netatmo.NACamera.description = This represents a welcome camera at home.
|
||||
|
||||
#thing-type.netatmo.NAWelcomePerson.label = Welcome Person
|
||||
#thing-type.netatmo.NAWelcomePerson.description = This represents a person at home.
|
||||
|
||||
# thing types config
|
||||
thing-type.config.netatmo.station.id.label = MAC-Adresse Modul
|
||||
thing-type.config.netatmo.station.id.description = MAC-Adresse des Haupt-Indoor-Moduls.
|
||||
|
||||
thing-type.config.netatmo.module.id.label = MAC-Adresse Zusatzmodule
|
||||
thing-type.config.netatmo.module.id.description = MAC-Adresse des Zusatzmoduls.
|
||||
|
||||
thing-type.config.netatmo.module.parentId.label = MAC-Adresse Haupt-Indoor-Modul
|
||||
thing-type.config.netatmo.module.parentId.description = MAC-Adresse des Haupt-Indoor-Moduls.
|
||||
|
||||
#thing-type.config.netatmo.plug.id.label = Equipment ID
|
||||
#thing-type.config.netatmo.plug.id.description = Id of the Device (mac address).
|
||||
|
||||
#thing-type.config.netatmo.natherm1.id.label = Module ID
|
||||
#thing-type.config.netatmo.natherm1.id.description = Id of the Module.
|
||||
|
||||
#thing-type.config.netatmo.natherm1.parentId.label = Device ID
|
||||
#thing-type.config.netatmo.natherm1.parentId.description = Id of the main device.
|
||||
|
||||
#thing-type.config.netatmo.natherm1.setpointDefaultDuration.label = Setpoint duration
|
||||
#thing-type.config.netatmo.natherm1.setpointDefaultDuration.description = Default duration of thermostat change when force to max or manual.
|
||||
|
||||
#thing-type.config.netatmo.welcomehome.id.label = Home ID
|
||||
#thing-type.config.netatmo.welcomehome.id.description = UUID of the home
|
||||
|
||||
#thing-type.config.netatmo.welcomehome.refreshInterval.label = Refresh Interval
|
||||
#thing-type.config.netatmo.welcomehome.refreshInterval.description = The refresh interval to poll Netatmo API (in ms).
|
||||
|
||||
#thing-type.config.netatmo.camera.id.label = Camera ID
|
||||
#thing-type.config.netatmo.camera.id.description = Camera MAC Address
|
||||
|
||||
#thing-type.config.netatmo.camera.parentId.label = Home ID
|
||||
#thing-type.config.netatmo.camera.parentId.description = UUID of the home hosting the camera
|
||||
|
||||
#thing-type.config.netatmo.nawelcomeperson.id.label = Person ID
|
||||
#thing-type.config.netatmo.nawelcomeperson.id.description = UUID of the Person
|
||||
|
||||
#thing-type.config.netatmo.nawelcomeperson.parentId.label = Home ID
|
||||
#thing-type.config.netatmo.nawelcomeperson.parentId.description = UUID of the home
|
||||
|
||||
# channel types
|
||||
channel-type.netatmo.co2.label = CO2-Gehalt
|
||||
channel-type.netatmo.co2.description = Zeigt den aktuellen CO2-Gehalt an.
|
||||
|
||||
channel-type.netatmo.temperature.label = Temperatur
|
||||
channel-type.netatmo.temperature.description = Zeigt die aktuelle Temperatur an.
|
||||
|
||||
channel-type.netatmo.minTemp.label = Min. Temperatur
|
||||
channel-type.netatmo.minTemp.description = Zeigt die minimale Temperatur des aktuellen Tages an.
|
||||
|
||||
channel-type.netatmo.maxTemp.label = Max. Temperatur
|
||||
channel-type.netatmo.maxTemp.description = Zeigt die maximale Temperatur des aktuellen Tages an.
|
||||
|
||||
channel-type.netatmo.dateMinTemp.label = Zeitpunkt Min. Temperatur
|
||||
channel-type.netatmo.dateMinTemp.description = Zeigt den Zeitpunkt an, an dem die minimale Temperatur gemessen wurde.
|
||||
|
||||
channel-type.netatmo.dateMaxTemp.label = Zeitpunkt Max. Temperatur
|
||||
channel-type.netatmo.dateMaxTemp.description = Zeigt den Zeitpunkt an, an dem die maximale Temperatur gemessen wurde.
|
||||
|
||||
channel-type.netatmo.temperatureTrend.label = Temperaturtrend
|
||||
channel-type.netatmo.temperatureTrend.description = Zeigt den Temperaturtrend (z.B. "Steigend", "Stabil" oder "Fallend") an.
|
||||
channel-type.netatmo.temperatureTrend.state.option.up = Steigend
|
||||
channel-type.netatmo.temperatureTrend.state.option.stable = Stabil
|
||||
channel-type.netatmo.temperatureTrend.state.option.down = Fallend
|
||||
|
||||
channel-type.netatmo.humidity.label = Luftfeuchtigkeit
|
||||
channel-type.netatmo.humidity.description = Zeigt die aktuelle Luftfeuchtigkeit an.
|
||||
|
||||
channel-type.netatmo.humidex.label = Gef. Temperatur (Humidex)
|
||||
channel-type.netatmo.humidex.description = Zeigt die gefühlte Temperatur an. Basierend auf dem Humidex.
|
||||
|
||||
channel-type.netatmo.heatIndex.label = Gef. Temperatur (Hitzeindex)
|
||||
channel-type.netatmo.heatIndex.description = Zeigt die gefühlte Temperatur an. Basierend auf dem Hitzeindex.
|
||||
|
||||
channel-type.netatmo.dewPoint.label = Taupunkt
|
||||
channel-type.netatmo.dewPoint.description = Zeigt den Taupunkt an.
|
||||
|
||||
channel-type.netatmo.dewPointDepression.label = Taupunktsdifferenz
|
||||
channel-type.netatmo.dewPointDepression.description = Zeigt die Taupunktsdifferenz an.
|
||||
|
||||
channel-type.netatmo.noise.label = Sonometer
|
||||
channel-type.netatmo.noise.description = Zeigt die aktuelle Lautstärke an.
|
||||
|
||||
channel-type.netatmo.pressure.label = Luftdruck
|
||||
channel-type.netatmo.pressure.description = Zeigt den aktuellen Luftdruck an.
|
||||
|
||||
channel-type.netatmo.pressureTrend.label = Luftdrucktrend
|
||||
channel-type.netatmo.pressureTrend.description = Zeigt den Luftdrucktrend an (z.B. "Steigend", "Stabil" oder "Fallend").
|
||||
channel-type.netatmo.pressureTrend.state.option.up = Steigend
|
||||
channel-type.netatmo.pressureTrend.state.option.stable = Stabil
|
||||
channel-type.netatmo.pressureTrend.state.option.down = Fallend
|
||||
|
||||
channel-type.netatmo.absolutePressure.label = Absoluter Luftdruck
|
||||
channel-type.netatmo.absolutePressure.description = Zeigt den absoluten Luftdruck an.
|
||||
|
||||
channel-type.netatmo.lastStatusStore.label = Letzte Übertragung
|
||||
channel-type.netatmo.lastStatusStore.description = Zeigt den Zeitpunkt der letzten Datenübertragung an.
|
||||
|
||||
channel-type.netatmo.timeUtc.label = Letzte Messung
|
||||
channel-type.netatmo.timeUtc.description = Zeigt den Zeitpunkt der letzten Datenmessung an.
|
||||
|
||||
channel-type.netatmo.lastMessage.label = Letzte Meldung
|
||||
channel-type.netatmo.lastMessage.description = Zeigt den Zeitpunkt der letzten Meldung an.
|
||||
|
||||
channel-type.netatmo.location.label = Standort
|
||||
channel-type.netatmo.location.description = Zeigt die geographischen Koordinaten (Breitengrad, Längengrad, Höhe über dem Meerespiegel) (in °N, °W, m) an.
|
||||
|
||||
channel-type.netatmo.rain.label = Niederschlag
|
||||
channel-type.netatmo.rain.description = Zeigt den aktuellen Niederschlag an.
|
||||
|
||||
channel-type.netatmo.rain1.label = Kumulierter Niederschlag (1h)
|
||||
channel-type.netatmo.rain1.description = Zeigt den kumulierten Niederschlag der letzten Stunde an.
|
||||
|
||||
channel-type.netatmo.rain24.label = Kumulierter Niederschlag (24h)
|
||||
channel-type.netatmo.rain24.description = Zeigt den kumulierten Niederschlag der letzten 24h an.
|
||||
|
||||
channel-type.netatmo.WindAngle.label = Windrichtung
|
||||
channel-type.netatmo.WindAngle.description = Zeigt die aktuelle Windrichtung an.
|
||||
|
||||
channel-type.netatmo.WindStrength.label = Windgeschwindigkeit
|
||||
channel-type.netatmo.WindStrength.description = Zeigt die aktuelle Windgeschwindigkeit an.
|
||||
|
||||
channel-type.netatmo.GustAngle.label = Böen Richtung
|
||||
channel-type.netatmo.GustAngle.description = Zeigt die aktuelle Böen Richtung an.
|
||||
|
||||
channel-type.netatmo.GustStrength.label = Böen Geschwindigkeit
|
||||
channel-type.netatmo.GustStrength.description = Zeigt die aktuelle Böen Geschwindigkeit an.
|
||||
|
||||
channel-type.netatmo.healthindex.label = Health Index
|
||||
channel-type.netatmo.healthindex.description = Zeigt den Health Index (z.B. "Gesund", "Gut", "Angemessen", "Schlecht" oder "Ungesund") an.
|
||||
channel-type.netatmo.healthindex.state.option.healthy = Gesund
|
||||
channel-type.netatmo.healthindex.state.option.fine = Gut
|
||||
channel-type.netatmo.healthindex.state.option.fair = Angemessen
|
||||
channel-type.netatmo.healthindex.state.option.poor = Schlecht
|
||||
channel-type.netatmo.healthindex.state.option.unhealthy = Ungesund
|
||||
|
||||
#channel-type.netatmo.connectedBoiler.label = Plug Connected Boiler
|
||||
#channel-type.netatmo.connectedBoiler.description =
|
||||
|
||||
#channel-type.netatmo.lastPlugSeen.label = Last Plug Seen
|
||||
#channel-type.netatmo.lastPlugSeen.description = Last Plug Seen
|
||||
|
||||
#channel-type.netatmo.lastBilan.label = Available Bilan
|
||||
#channel-type.netatmo.lastBilan.description = Month of the last available thermostat bilan
|
||||
|
||||
#channel-type.netatmo.setpointTemp.label = Setpoint
|
||||
#channel-type.netatmo.setpointTemp.description = Thermostat temperature setpoint
|
||||
|
||||
#channel-type.netatmo.setpointMode.label = Setpoint Mode
|
||||
#channel-type.netatmo.setpointMode.description = Chosen setpoint_mode (program, away, hg, manual, off, max)
|
||||
#channel-type.netatmo.setpointMode.state.option.program = Following a weekly schedule
|
||||
#channel-type.netatmo.setpointMode.state.option.away = Applying the -away- temperature as defined by the user
|
||||
#channel-type.netatmo.setpointMode.state.option.hg = Frost-guard
|
||||
#channel-type.netatmo.setpointMode.state.option.manual = Applying a manually set temperature setpoint
|
||||
#channel-type.netatmo.setpointMode.state.option.off = Currently off
|
||||
#channel-type.netatmo.setpointMode.state.option.max = Heating continuously
|
||||
|
||||
#channel-type.netatmo.ThermRelayCmd.label = Heating status
|
||||
#channel-type.netatmo.ThermRelayCmd.description = Indicates whether the furnace is heating or not
|
||||
|
||||
#channel-type.netatmo.ThermOrientation.label = Orientation
|
||||
#channel-type.netatmo.ThermOrientation.description = Physical orientation of the thermostat module
|
||||
|
||||
#channel-type.netatmo.setpointEndTime.label = Setpoint end
|
||||
#channel-type.netatmo.setpointEndTime.description = Thermostat goes back to schedule after that timestamp.
|
||||
|
||||
#channel-type.netatmo.homecity.label = City
|
||||
#channel-type.netatmo.homecity.description = City of the home
|
||||
|
||||
#channel-type.netatmo.homecountry.label = Country
|
||||
#channel-type.netatmo.homecountry.description = Country of the home
|
||||
|
||||
#channel-type.netatmo.hometimezone.label = Timezone
|
||||
#channel-type.netatmo.hometimezone.description = Timezone of the home
|
||||
|
||||
#channel-type.netatmo.homepersoncount.label = Person counter
|
||||
#channel-type.netatmo.homepersoncount.description = Total number of Persons that are at home
|
||||
|
||||
#channel-type.netatmo.homeunknowncount.label = Unknown Person counter
|
||||
#channel-type.netatmo.homeunknowncount.description = Count how many Unknown Persons are at home
|
||||
|
||||
#channel-type.netatmo.type.label = Type
|
||||
#channel-type.netatmo.type.description = Type of event
|
||||
|
||||
#channel-type.netatmo.time.label = Time
|
||||
#channel-type.netatmo.time.description = Time of occurrence of event
|
||||
|
||||
#channel-type.netatmo.camera_id.label = Camera ID
|
||||
#channel-type.netatmo.camera_id.description = Camera that detected the event
|
||||
|
||||
#channel-type.netatmo.person_id.label = Person ID
|
||||
#channel-type.netatmo.person_id.description = Id of the person the event is about (if any)
|
||||
|
||||
#channel-type.netatmo.snapshot_url.label = Snapshot URL
|
||||
#channel-type.netatmo.snapshot_url.description = Url of the event snapshot
|
||||
|
||||
#channel-type.netatmo.snapshot.label = Event Snapshot
|
||||
#channel-type.netatmo.snapshot.description = Event Snapshot
|
||||
|
||||
#channel-type.netatmo.video_url.label = Video URL
|
||||
#channel-type.netatmo.video_url.description = URL of the event video
|
||||
|
||||
#channel-type.netatmo.video_status.label = Video status
|
||||
#channel-type.netatmo.video_status.description = Status of the video (recording, deleted or available)
|
||||
|
||||
#channel-type.netatmo.is_arrival.label = Is arrival
|
||||
#channel-type.netatmo.is_arrival.description = If person was considered "away" before being seen during this event
|
||||
|
||||
#channel-type.netatmo.message.label = Message
|
||||
#channel-type.netatmo.message.description = Message sent by Netatmo corresponding to given event
|
||||
|
||||
#channel-type.netatmo.sub_type.label = Sub Type
|
||||
#channel-type.netatmo.sub_type.description = Sub-type of SD and Alim events
|
||||
|
||||
#channel-type.netatmo.status.label = State
|
||||
#channel-type.netatmo.status.description = State of the camera
|
||||
|
||||
#channel-type.netatmo.sd_status.label = SD State
|
||||
#channel-type.netatmo.sd_status.description = State of the SD card
|
||||
|
||||
#channel-type.netatmo.alim_status.label = Alim State
|
||||
#channel-type.netatmo.alim_status.description = State of the power connector
|
||||
|
||||
#channel-type.netatmo.is_locale.label = Is local
|
||||
#channel-type.netatmo.is_locale.description = Indicates whether the camera is on the same network than the openHab Netatmo Binding
|
||||
|
||||
#channel-type.netatmo.live_picture_url.label = Live snapshot URL
|
||||
#channel-type.netatmo.live_picture_url.description = Url of the live snapshot for this camera
|
||||
|
||||
#channel-type.netatmo.live_picture.label = Live Snapshot
|
||||
#channel-type.netatmo.live_picture.description = Camera Live Snapshot
|
||||
|
||||
#channel-type.netatmo.live_stream_url.label = Live stream URL
|
||||
#channel-type.netatmo.live_stream_url.description = Url of the live stream for this camera
|
||||
|
||||
#channel-type.netatmo.last_seen.label = Last seen
|
||||
#channel-type.netatmo.last_seen.description = Time when this person was last seen
|
||||
|
||||
#channel-type.netatmo.person_athome.label = At home
|
||||
#channel-type.netatmo.person_athome.description = Indicates if this person is known to be at home or not
|
||||
|
||||
#channel-type.netatmo.person_eventmsg.label = Last Event message
|
||||
#channel-type.netatmo.person_eventmsg.description = Last Event message from this person
|
||||
|
||||
#channel-type.netatmo.person_eventtime.label = Last Event time
|
||||
#channel-type.netatmo.person_eventtime.description = Last Event message time for this person
|
||||
|
||||
#channel-type.netatmo.person_avatar_url.label = Avatar URL
|
||||
#channel-type.netatmo.person_avatar_url.description = URL for the avatar of this person
|
||||
|
||||
#channel-type.netatmo.person_avatar.label = Avatar
|
||||
#channel-type.netatmo.person_avatar.description = Avatar of this person
|
||||
|
||||
#channel-type.netatmo.person_event.label = Last Event Picture
|
||||
#channel-type.netatmo.person_event.description = Picture of the last event for this person
|
||||
|
||||
#channel-type.netatmo.person_event_url.label = Last event URL
|
||||
#channel-type.netatmo.person_event_url.description = URL for the picture of the last event for this person
|
||||
@@ -0,0 +1,378 @@
|
||||
# binding
|
||||
binding.netatmo.name = Extension Netatmo
|
||||
binding.netatmo.description = L'extension Netatmo intègre le thermostat et la station météo Netatmo ainsi que les modules additionels.
|
||||
|
||||
# bridge types
|
||||
thing-type.netatmo.netatmoapi.label = API Netatmo
|
||||
thing-type.netatmo.netatmoapi.description = Cet élément représente la passerelle avec l'API Netatmo.
|
||||
|
||||
# bridge types configuration
|
||||
thing-type.config.netatmo.bridge.clientId.label = ID client
|
||||
thing-type.config.netatmo.bridge.clientId.description = ID clent fourni par l'application que vous avez créée sur http://dev.netatmo.com/createapp
|
||||
thing-type.config.netatmo.bridge.clientSecret.label = Secret client
|
||||
thing-type.config.netatmo.bridge.clientSecret.description = Secret client fourni par l'application que vous avez créée.
|
||||
|
||||
thing-type.config.netatmo.bridge.username.label = Nom d'utilisateur
|
||||
thing-type.config.netatmo.bridge.username.description = Votre nom d'utilisateur (e-mail) pour l'API Netatmo.
|
||||
thing-type.config.netatmo.bridge.password.label = Mot de passe
|
||||
thing-type.config.netatmo.bridge.password.description = Votre mot de passe pour l'API Netatmo.
|
||||
|
||||
thing-type.config.netatmo.bridge.readStation.label = Accès à la station météo
|
||||
thing-type.config.netatmo.bridge.readStation.description = Accède ou non aux données de la station météo.
|
||||
|
||||
thing-type.config.netatmo.bridge.readHealthyHomeCoach.label = Accès au Healthy Home Coach
|
||||
thing-type.config.netatmo.bridge.readHealthyHomeCoach.description = Accède ou non aux données du Healthy Home Coach.
|
||||
|
||||
thing-type.config.netatmo.bridge.readThermostat.label = Accès au thermostat
|
||||
thing-type.config.netatmo.bridge.readThermostat.description = Accède ou non aux données du thermostat.
|
||||
|
||||
thing-type.config.netatmo.bridge.readWelcome.label = Accès à la Camera Welcome
|
||||
thing-type.config.netatmo.bridge.readWelcome.description = Accède ou non aux données de la caméra Welcome.
|
||||
|
||||
thing-type.config.netatmo.bridge.readPresence.label = Accès à la Camera Presence
|
||||
thing-type.config.netatmo.bridge.readPresence.description = Accède ou non aux données de la caméra Presence.
|
||||
|
||||
thing-type.config.netatmo.bridge.webHookUrl.label = Adresse Webhook
|
||||
thing-type.config.netatmo.bridge.webHookUrl.description = Protocole, IP publique et port pour l'accès au sereveur OH2 depuis l'Internet.
|
||||
|
||||
thing-type.config.netatmo.bridge.reconnectInterval.label = Intervalle de reconnection
|
||||
thing-type.config.netatmo.bridge.reconnectInterval.description = L'intervalle de reconnection à l'API Netatmo (en s).
|
||||
|
||||
# thing types
|
||||
thing-type.netatmo.NAMain.label = Station intérieure principale
|
||||
thing-type.netatmo.NAMain.description = Module capable de mesurer la température, l'humidité, la pression, la qualité de l'air et le niveau sonore.
|
||||
|
||||
thing-type.netatmo.NAModule1.label = Module Extérieur
|
||||
thing-type.netatmo.NAModule1.description = Module extérieur capable de mesurer la température et l'humidité.
|
||||
|
||||
thing-type.netatmo.NAModule2.label = Anémomètre
|
||||
thing-type.netatmo.NAModule2.description = Module extérieur dédié au vent capable de mesurer sa direction et sa force.
|
||||
|
||||
thing-type.netatmo.NAModule3.label = Capteur de Pluie
|
||||
thing-type.netatmo.NAModule3.description = Module extérieur dédié à la mesure du niveau de précipitations.
|
||||
|
||||
thing-type.netatmo.NAModule4.label = Module Additionel
|
||||
thing-type.netatmo.NAModule4.description = Module intérieur supplémentaire capable de mesurer la température, l'humidité et le niveau de CO2.
|
||||
|
||||
thing-type.netatmo.NAPlug.label = Relais Thermostat
|
||||
thing-type.netatmo.NAPlug.description = Cet élément représente le relais communiquant avec le thermostat.
|
||||
|
||||
thing-type.netatmo.NATherm1.label = Module Thermostat
|
||||
thing-type.netatmo.NATherm1.description = Cet élément représente le module thermostat permettant de règler la température.
|
||||
|
||||
thing-type.netatmo.NHC.label = Healthy Home Coach
|
||||
thing-type.netatmo.NHC.description = Cet élément représente le module Healthy Home Coach capable de mesurer le niveau de confort dans la maison.
|
||||
|
||||
thing-type.netatmo.NAWelcomeHome.label = Maison
|
||||
thing-type.netatmo.NAWelcomeHome.description = Cet élément représente une maison hébergeant une ou plusieurs caméras Netatmo.
|
||||
|
||||
thing-type.netatmo.NACamera.label = Caméra Welcome
|
||||
thing-type.netatmo.NACamera.description = Cet élément représente une caméra Welcome de la maison.
|
||||
|
||||
thing-type.netatmo.NAWelcomePerson.label = Personne
|
||||
thing-type.netatmo.NAWelcomePerson.description = Cet élément représente une personne de la maison.
|
||||
|
||||
# thing type configuration
|
||||
thing-type.config.netatmo.station.id.label = ID équipement
|
||||
thing-type.config.netatmo.station.id.description = ID de l'équipement (adresse MAC)
|
||||
|
||||
thing-type.config.netatmo.module.id.label = ID module
|
||||
thing-type.config.netatmo.module.id.description = ID du module
|
||||
|
||||
thing-type.config.netatmo.module.parentId.label = ID équipement principal
|
||||
thing-type.config.netatmo.module.parentId.description = ID de l'équipement principal
|
||||
|
||||
thing-type.config.netatmo.plug.id.label = ID équipement
|
||||
thing-type.config.netatmo.plug.id.description = ID de l'équipement (adresse MAC)
|
||||
|
||||
thing-type.config.netatmo.natherm1.id.label = ID module
|
||||
thing-type.config.netatmo.natherm1.id.description = ID du module
|
||||
|
||||
thing-type.config.netatmo.natherm1.parentId.label = ID équipement principal
|
||||
thing-type.config.netatmo.natherm1.parentId.description = ID de l'équipement principal
|
||||
|
||||
thing-type.config.netatmo.natherm1.setpointDefaultDuration.label = Durée de consigne
|
||||
thing-type.config.netatmo.natherm1.setpointDefaultDuration.description = Durée par défaut de consigne hors planning
|
||||
|
||||
thing-type.config.netatmo.welcomehome.id.label = ID maison
|
||||
thing-type.config.netatmo.welcomehome.id.description = ID de la maison
|
||||
|
||||
thing-type.config.netatmo.welcomehome.refreshInterval.label = Fréquence de rafraîchissement
|
||||
thing-type.config.netatmo.welcomehome.refreshInterval.description = La fréquence d'interrogation de l'API Netatmo (en ms)
|
||||
|
||||
thing-type.config.netatmo.camera.id.label = ID caméra
|
||||
thing-type.config.netatmo.camera.id.description = ID de la caméra (adresse MAC)
|
||||
|
||||
thing-type.config.netatmo.camera.parentId.label = ID maison
|
||||
thing-type.config.netatmo.camera.parentId.description = ID de la maison hébergeant la caméra
|
||||
|
||||
thing-type.config.netatmo.nawelcomeperson.id.label = ID personne
|
||||
thing-type.config.netatmo.nawelcomeperson.id.description = ID de la personne
|
||||
|
||||
thing-type.config.netatmo.nawelcomeperson.parentId.label = ID maison
|
||||
thing-type.config.netatmo.nawelcomeperson.parentId.description = ID de la maison
|
||||
|
||||
# channel types
|
||||
channel-type.netatmo.co2.label = CO2
|
||||
channel-type.netatmo.co2.description = Mesure de la qualité de l'air
|
||||
|
||||
channel-type.netatmo.temperature.label = Température
|
||||
channel-type.netatmo.temperature.description = Mesure de la tempérture
|
||||
|
||||
channel-type.netatmo.temperatureTrend.label = Tendance température
|
||||
channel-type.netatmo.temperatureTrend.description = Mesure de la tendance d'évolution de la tempérture
|
||||
channel-type.netatmo.temperatureTrend.state.option.up = A la hausse
|
||||
channel-type.netatmo.temperatureTrend.state.option.stable = Stable
|
||||
channel-type.netatmo.temperatureTrend.state.option.down = A la baisse
|
||||
|
||||
channel-type.netatmo.noise.label = Niveau sonore
|
||||
channel-type.netatmo.noise.description = Mesure du niveau sonore de la pièce
|
||||
|
||||
channel-type.netatmo.pressure.label = Pression atmosphérique
|
||||
channel-type.netatmo.pressure.description = Mesure de la pression atmosphérique
|
||||
|
||||
channel-type.netatmo.pressureTrend.label = Tendance pression atmosphérique
|
||||
channel-type.netatmo.pressureTrend.description = Mesure de la tendance d'évolution de la pression atmosphérique sur les dernières 12 heures
|
||||
channel-type.netatmo.pressureTrend.state.option.up = A la hausse
|
||||
channel-type.netatmo.pressureTrend.state.option.stable = Stable
|
||||
channel-type.netatmo.pressureTrend.state.option.down = A la baisse
|
||||
|
||||
channel-type.netatmo.absolutePressure.label = Pression atmosphérique absolue
|
||||
channel-type.netatmo.absolutePressure.description = Mesure de la pression atmosphérique absolue
|
||||
|
||||
channel-type.netatmo.timeUtc.label = Horodatage des mesures
|
||||
channel-type.netatmo.timeUtc.description = Date/Heure du dernier relevé de mesures
|
||||
|
||||
channel-type.netatmo.humidity.label = Humidité
|
||||
channel-type.netatmo.humidity.description = Mesure du niveau d'hygrométrie
|
||||
|
||||
channel-type.netatmo.humidex.label = Humidex
|
||||
channel-type.netatmo.humidex.description = Indice humidex calculé
|
||||
|
||||
channel-type.netatmo.heatIndex.label = Indice de chaleur
|
||||
channel-type.netatmo.heatIndex.description = Indice de chaleur calculé
|
||||
|
||||
channel-type.netatmo.dewPoint.label = Point de rosée
|
||||
channel-type.netatmo.dewPoint.description = Température du point de rosée
|
||||
|
||||
channel-type.netatmo.dewPointDepression.label = Dépression du point de rosée
|
||||
channel-type.netatmo.dewPointDepression.description = Ecart entre la température actuelle et le point de rosée
|
||||
|
||||
channel-type.netatmo.minTemp.label = Température min
|
||||
channel-type.netatmo.minTemp.description = Température minimale de la journée en cours
|
||||
|
||||
channel-type.netatmo.maxTemp.label = Température max
|
||||
channel-type.netatmo.maxTemp.description = Température maximale de la journée en cours
|
||||
|
||||
channel-type.netatmo.dateMinTemp.label = Horodatage température min
|
||||
channel-type.netatmo.dateMinTemp.description = Date/Heure de relevé de la température minimale pour la journée en cours
|
||||
|
||||
channel-type.netatmo.dateMaxTemp.label = Horodatage température max
|
||||
channel-type.netatmo.dateMaxTemp.description = Date/Heure de relevé de la température maximale pour la journée en cours
|
||||
|
||||
channel-type.netatmo.location.label = Localisation
|
||||
channel-type.netatmo.location.description = Localisation de la station Netatmo
|
||||
|
||||
channel-type.netatmo.rain.label = Précipitations
|
||||
channel-type.netatmo.rain.description = Volume de précipitations relevé
|
||||
|
||||
channel-type.netatmo.rain1.label = Précipitations 1h
|
||||
channel-type.netatmo.rain1.description = Volume de précipitations relevé durant la dernière heure
|
||||
|
||||
channel-type.netatmo.rain24.label = Précipitations 24h
|
||||
channel-type.netatmo.rain24.description = Volume de précipitations relevé durant la dernière journée
|
||||
|
||||
channel-type.netatmo.WindAngle.label = Direction du vent
|
||||
channel-type.netatmo.WindAngle.description = Direction moyenne du vent sur les 5 dernières minutes
|
||||
|
||||
channel-type.netatmo.WindStrength.label = Force du vent
|
||||
channel-type.netatmo.WindStrength.description = Vitesse moyenne du vent sur les 5 dernières minutes
|
||||
|
||||
channel-type.netatmo.GustAngle.label = Direction rafale de vent
|
||||
channel-type.netatmo.GustAngle.description = Direction moyenne des rafales de vent sur les 5 dernières minutes
|
||||
|
||||
channel-type.netatmo.GustStrength.label = Force rafale de vent
|
||||
channel-type.netatmo.GustStrength.description = Vitesse moyenne des rafales de vent sur les 5 dernières minutes
|
||||
|
||||
channel-type.netatmo.lastStatusStore.label = Dernière demande d'état
|
||||
channel-type.netatmo.lastStatusStore.description = Date/Heure de la dernière demande d'état
|
||||
|
||||
channel-type.netatmo.lastMessage.label = Horodatage dernier message
|
||||
channel-type.netatmo.lastMessage.description = Date/Heure du dernier message émis par le module
|
||||
|
||||
channel-type.netatmo.connectedBoiler.label = Relais connecté
|
||||
channel-type.netatmo.connectedBoiler.description = Indique si le relais est connecté ou non à une chaudière
|
||||
|
||||
channel-type.netatmo.lastPlugSeen.label = Horodatage visibilité du relais
|
||||
channel-type.netatmo.lastPlugSeen.description = Date/Heure de dernière visibilité du module relais par le thermostat
|
||||
|
||||
channel-type.netatmo.lastBilan.label = Bilan Economies d'Energie
|
||||
channel-type.netatmo.lastBilan.description = Mois du dernier bilan d'économies d'énergie disponible
|
||||
|
||||
channel-type.netatmo.setpointTemp.label = Température de consigne
|
||||
channel-type.netatmo.setpointTemp.description = Température de consigne sélectionnée sur le thermostat
|
||||
|
||||
channel-type.netatmo.setpointMode.label = Mode de consigne
|
||||
channel-type.netatmo.setpointMode.description = Mode de consigne choisi sur le thermostat (planning hebdo, absence, hors-gel, manuel, arrêt, en permanence)
|
||||
channel-type.netatmo.setpointMode.state.option.program = Suivi du planning hebdomadaire
|
||||
channel-type.netatmo.setpointMode.state.option.away = Température d'absence
|
||||
channel-type.netatmo.setpointMode.state.option.hg = Hors-gel
|
||||
channel-type.netatmo.setpointMode.state.option.manual = Température de consigne manuelle
|
||||
channel-type.netatmo.setpointMode.state.option.off = Arrêt
|
||||
channel-type.netatmo.setpointMode.state.option.max = Chauffage en permanence
|
||||
|
||||
channel-type.netatmo.planning.label = Planning
|
||||
channel-type.netatmo.planning.description = Planification des plages de chauffe utilisée en mode suivi du planning
|
||||
|
||||
channel-type.netatmo.ThermRelayCmd.label = Etat du chauffage
|
||||
channel-type.netatmo.ThermRelayCmd.description = Indique si le chauffage est en marche ou pas
|
||||
|
||||
channel-type.netatmo.ThermOrientation.label = Orientation
|
||||
channel-type.netatmo.ThermOrientation.description = Orientation physique du module thermostat
|
||||
|
||||
channel-type.netatmo.setpointEndTime.label = Heure fin de consigne
|
||||
channel-type.netatmo.setpointEndTime.description = Heure de retour au planning de chauffe
|
||||
|
||||
channel-type.netatmo.healthindex.label = Indice de confort
|
||||
channel-type.netatmo.healthindex.description = Indice de confort (sain, agréable, correct, mauvais, malsain)
|
||||
channel-type.netatmo.healthindex.state.option.healthy = Sain
|
||||
channel-type.netatmo.healthindex.state.option.fine = Agréable
|
||||
channel-type.netatmo.healthindex.state.option.fair = Correct
|
||||
channel-type.netatmo.healthindex.state.option.poor = Mauvais
|
||||
channel-type.netatmo.healthindex.state.option.unhealthy = Malsain
|
||||
|
||||
channel-type.netatmo.homecity.label = Ville
|
||||
channel-type.netatmo.homecity.description = Ville
|
||||
|
||||
channel-type.netatmo.homecountry.label = Pays
|
||||
channel-type.netatmo.homecountry.description = Pays
|
||||
|
||||
channel-type.netatmo.hometimezone.label = Fuseau horaire
|
||||
channel-type.netatmo.hometimezone.description = Fuseau horaire
|
||||
|
||||
channel-type.netatmo.homepersoncount.label = Compteur de personnes
|
||||
channel-type.netatmo.homepersoncount.description = Nombre de personnes qui sont à la maison
|
||||
|
||||
channel-type.netatmo.homeunknowncount.label = Compteur de personnes inconnues
|
||||
channel-type.netatmo.homeunknowncount.description = Nombre de personnes inconnues qui sont à la maison
|
||||
|
||||
channel-type.netatmo.type.label = Type de l'évènement
|
||||
channel-type.netatmo.type.description = Type du dernier évènement
|
||||
|
||||
channel-type.netatmo.time.label = Horodatage de l'évènement
|
||||
channel-type.netatmo.time.description = Date/Heure du dernier évènement
|
||||
|
||||
channel-type.netatmo.camera_id.label = ID caméra
|
||||
channel-type.netatmo.camera_id.description = Caméra à l'origine du dernier évènement
|
||||
|
||||
channel-type.netatmo.person_id.label = ID personne
|
||||
channel-type.netatmo.person_id.description = Id de la personne concernée par le dernier évènement
|
||||
|
||||
channel-type.netatmo.snapshot_url.label = URL image capturée
|
||||
channel-type.netatmo.snapshot_url.description = Url de l'image capturée (quand une image est associée au dernier évènement)
|
||||
|
||||
channel-type.netatmo.snapshot.label = Image capturée
|
||||
channel-type.netatmo.snapshot.description = Image capturée (quand une image est associée au dernier évènement)
|
||||
|
||||
channel-type.netatmo.video_url.label = URL vidéo capturée
|
||||
channel-type.netatmo.video_url.description = Url de la vidéo capturée (quand une vidéo est associée au dernier évènement)
|
||||
|
||||
channel-type.netatmo.video_status.label = Etat de la vidéo
|
||||
channel-type.netatmo.video_status.description = Etat de la vidéo associée au dernier évènement (recording, deleted or available)
|
||||
|
||||
channel-type.netatmo.is_arrival.label = Personne arrivant
|
||||
channel-type.netatmo.is_arrival.description = Si cet évènement indique la détection d'une personne qui était considérée comme absente auparavant
|
||||
|
||||
channel-type.netatmo.message.label = Message de l'évènement
|
||||
channel-type.netatmo.message.description = Message correspondant au dernier évènement
|
||||
|
||||
channel-type.netatmo.sub_type.label = Sous-type de l'évènement
|
||||
channel-type.netatmo.sub_type.description = Sous-type du dernier évènement (disponible uniquement pour certains évènements)
|
||||
|
||||
channel-type.netatmo.status.label = Etat de la caméra
|
||||
channel-type.netatmo.status.description = Etat de la caméra
|
||||
|
||||
channel-type.netatmo.sd_status.label = Etat de la carte SD
|
||||
channel-type.netatmo.sd_status.description = Etat de la carte SD
|
||||
|
||||
channel-type.netatmo.alim_status.label = Etat de l'alimentation
|
||||
channel-type.netatmo.alim_status.description = Etat de l'alimentation
|
||||
|
||||
channel-type.netatmo.is_locale.label = Caméra locale
|
||||
channel-type.netatmo.is_locale.description = Indique si la caméra est dans le même réseau local que le logiciel
|
||||
|
||||
channel-type.netatmo.live_picture_url.label = URL image en direct
|
||||
channel-type.netatmo.live_picture_url.description = Url de l'image en direct de la caméra
|
||||
|
||||
channel-type.netatmo.live_picture.label = Image en direct
|
||||
channel-type.netatmo.live_picture.description = Image en direct de la caméra
|
||||
|
||||
channel-type.netatmo.live_stream_url.label = URL flux vidéo en direct
|
||||
channel-type.netatmo.live_stream_url.description = Url du flux vidéo en direct de la caméra
|
||||
|
||||
channel-type.netatmo.last_seen.label = Date dernière détection
|
||||
channel-type.netatmo.last_seen.description = Date où cette personne a été reconnue pour la dernière fois
|
||||
|
||||
channel-type.netatmo.person_athome.label = A la maison
|
||||
channel-type.netatmo.person_athome.description = Indique si cette personne est connue comme étant ou non à la masioon
|
||||
|
||||
channel-type.netatmo.person_eventmsg.label = Dernier message
|
||||
channel-type.netatmo.person_eventmsg.description = Dernier message relatif à cette personne
|
||||
|
||||
channel-type.netatmo.person_eventtime.label = Date dernier message
|
||||
channel-type.netatmo.person_eventtime.description = Date du dernier message relatif à cette personne
|
||||
|
||||
channel-type.netatmo.person_avatar_url.label = URL avatar
|
||||
channel-type.netatmo.person_avatar_url.description = Url de l'avatar de la personne
|
||||
|
||||
channel-type.netatmo.person_avatar.label = Avatar
|
||||
channel-type.netatmo.person_avatar.description = Avatar de la personne
|
||||
|
||||
channel-type.netatmo.person_event.label = Dernière image
|
||||
channel-type.netatmo.person_event.description = Image associée au dernier évènement relatif à cette personne
|
||||
|
||||
channel-type.netatmo.person_event_url.label = URL de la dernière image
|
||||
channel-type.netatmo.person_event_url.description = URL de l'mage associée au dernier évènement relatif à cette personne
|
||||
|
||||
# Thing channels
|
||||
|
||||
thing-type.netatmo.NAMain.channel.WifiStatus.label = Niveau Wifi
|
||||
thing-type.netatmo.NAMain.channel.WifiStatus.description = Indicateur de la qualité de signal Wifi
|
||||
|
||||
thing-type.netatmo.NAPlug.channel.WifiStatus.label = Niveau Wifi
|
||||
thing-type.netatmo.NAPlug.channel.WifiStatus.description = Indicateur de la qualité de signal Wifi
|
||||
|
||||
thing-type.netatmo.NAModule1.channel.RfStatus.label = Niveau radio
|
||||
thing-type.netatmo.NAModule1.channel.RfStatus.description = Indicateur de la qualité de signal radio
|
||||
thing-type.netatmo.NAModule1.channel.BatteryVP.label = Niveau piles
|
||||
thing-type.netatmo.NAModule1.channel.BatteryVP.description = Indicateur du niveau de piles
|
||||
thing-type.netatmo.NAModule1.channel.LowBattery.label = Piles faibles
|
||||
thing-type.netatmo.NAModule1.channel.LowBattery.description = Indicateur du niveau faible des piles
|
||||
|
||||
thing-type.netatmo.NAModule2.channel.RfStatus.label = Niveau radio
|
||||
thing-type.netatmo.NAModule2.channel.RfStatus.description = Indicateur de la qualité de signal radio
|
||||
thing-type.netatmo.NAModule2.channel.BatteryVP.label = Niveau piles
|
||||
thing-type.netatmo.NAModule2.channel.BatteryVP.description = Indicateur du niveau de piles
|
||||
thing-type.netatmo.NAModule2.channel.LowBattery.label = Piles faibles
|
||||
thing-type.netatmo.NAModule2.channel.LowBattery.description = Indicateur du niveau faible des piles
|
||||
|
||||
thing-type.netatmo.NAModule3.channel.RfStatus.label = Niveau radio
|
||||
thing-type.netatmo.NAModule3.channel.RfStatus.description = Indicateur de la qualité de signal radio
|
||||
thing-type.netatmo.NAModule3.channel.BatteryVP.label = Niveau piles
|
||||
thing-type.netatmo.NAModule3.channel.BatteryVP.description = Indicateur du niveau de piles
|
||||
thing-type.netatmo.NAModule3.channel.LowBattery.label = Piles faibles
|
||||
thing-type.netatmo.NAModule3.channel.LowBattery.description = Indicateur du niveau faible des piles
|
||||
|
||||
thing-type.netatmo.NAModule4.channel.RfStatus.label = Niveau radio
|
||||
thing-type.netatmo.NAModule4.channel.RfStatus.description = Indicateur de la qualité de signal radio
|
||||
thing-type.netatmo.NAModule4.channel.BatteryVP.label = Niveau piles
|
||||
thing-type.netatmo.NAModule4.channel.BatteryVP.description = Indicateur du niveau de piles
|
||||
thing-type.netatmo.NAModule4.channel.LowBattery.label = Piles faibles
|
||||
thing-type.netatmo.NAModule4.channel.LowBattery.description = Indicateur du niveau faible des piles
|
||||
|
||||
thing-type.netatmo.NATherm1.channel.RfStatus.label = Niveau radio
|
||||
thing-type.netatmo.NATherm1.channel.RfStatus.description = Indicateur de la qualité de signal radio
|
||||
thing-type.netatmo.NATherm1.channel.BatteryVP.label = Niveau piles
|
||||
thing-type.netatmo.NATherm1.channel.BatteryVP.description = Indicateur du niveau de piles
|
||||
thing-type.netatmo.NATherm1.channel.LowBattery.label = Piles faibles
|
||||
thing-type.netatmo.NATherm1.channel.LowBattery.description = Indicateur du niveau faible des piles
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="netatmo"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- Netatmo API Bridge -->
|
||||
<bridge-type id="netatmoapi">
|
||||
<label>Netatmo API</label>
|
||||
<description>This bridge represents the gateway to Netatmo API.</description>
|
||||
<config-description-ref uri="thing-type:netatmo:bridge"/>
|
||||
</bridge-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="netatmo"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="NACamera">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Welcome Camera</label>
|
||||
<description>This represents a welcome camera at home</description>
|
||||
|
||||
<channels>
|
||||
<channel id="welcomeCameraStatus" typeId="status"></channel>
|
||||
<channel id="welcomeCameraSdStatus" typeId="sd_status"></channel>
|
||||
<channel id="welcomeCameraAlimStatus" typeId="alim_status"></channel>
|
||||
<channel id="welcomeCameraIsLocal" typeId="is_locale"></channel>
|
||||
<channel id="welcomeCameraLivePicture" typeId="live_picture"></channel>
|
||||
<channel id="welcomeCameraLivePictureUrl" typeId="live_picture_url"></channel>
|
||||
<channel id="welcomeCameraLiveStreamUrl" typeId="live_stream_url"></channel>
|
||||
<channel id="welcomeHomeEvent" typeId="homeEvent"></channel>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:camera"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="NOC">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Presence Camera</label>
|
||||
<description>This represents a presence camera at home</description>
|
||||
|
||||
<channels>
|
||||
<channel id="cameraStatus" typeId="status"></channel>
|
||||
<channel id="cameraSdStatus" typeId="sd_status"></channel>
|
||||
<channel id="cameraAlimStatus" typeId="alim_status"></channel>
|
||||
<channel id="cameraIsLocal" typeId="is_locale"></channel>
|
||||
<channel id="cameraLivePicture" typeId="live_picture"></channel>
|
||||
<channel id="cameraLivePictureUrl" typeId="live_picture_url"></channel>
|
||||
<channel id="cameraLiveStreamUrl" typeId="live_stream_url"></channel>
|
||||
<channel id="cameraFloodlightAutoMode" typeId="floodlightAutoMode"></channel>
|
||||
<channel id="cameraFloodlight" typeId="floodlight"></channel>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:camera"/>
|
||||
</thing-type>
|
||||
|
||||
|
||||
<channel-type id="status">
|
||||
<item-type>Switch</item-type>
|
||||
<label>State</label>
|
||||
<description>State of the camera</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sd_status">
|
||||
<item-type>Switch</item-type>
|
||||
<label>SD State</label>
|
||||
<description>State of the SD card</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alim_status">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Alim State</label>
|
||||
<description>State of the power connector</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="is_locale" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Is Local</label>
|
||||
<description>Only for scope access_camera. If Camera and application requesting the information are on the same
|
||||
network (true/false)</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="live_picture_url" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Live Snapshot URL</label>
|
||||
<description>Url of the live snapshot for this camera (need scope access_camera)</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="live_picture">
|
||||
<item-type>Image</item-type>
|
||||
<label>Live Snapshot</label>
|
||||
<description>Camera Live Snapshot</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="live_stream_url" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Live Stream URL</label>
|
||||
<description>Url of the live stream for this camera</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="floodlightAutoMode">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Floodlight Auto-Mode</label>
|
||||
<description>State of the floodlight auto-mode</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="floodlight">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Floodlight</label>
|
||||
<description>State of the floodlight</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,795 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="netatmo"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="lastStatusStore" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Status Store</label>
|
||||
<description>Last Status Store</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="location">
|
||||
<item-type>Location</item-type>
|
||||
<label>Location</label>
|
||||
<description>Location of the device</description>
|
||||
<state readOnly="true" pattern="%2$s°N,%3$s°W, %1$s m"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Current temperature</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minTemp" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Min Temp</label>
|
||||
<description>Minimum Temperature on current day</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minTempThisWeek" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Min Temp This Week</label>
|
||||
<description>Minimum Temperature this week</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minTempThisMonth" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Min Temp This Month</label>
|
||||
<description>Minimum Temperature this month</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxTemp" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Max Temp</label>
|
||||
<description>Maximum Temperature on current day</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxTempThisWeek" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Max Temp This Week</label>
|
||||
<description>Maximum Temperature this week</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxTempThisMonth" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Max Temp This Month</label>
|
||||
<description>Maximum Temperature this month</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperatureTrend" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Temp Trend</label>
|
||||
<description>Temperature Evolution Trend</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%s">
|
||||
<options>
|
||||
<option value="up">up</option>
|
||||
<option value="stable">stable</option>
|
||||
<option value="down">down</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="setpointTemp">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Setpoint</label>
|
||||
<description>Thermostat temperature setpoint</description>
|
||||
<category>Temperature</category>
|
||||
<state pattern="%.1f %unit%" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="setpointMode">
|
||||
<item-type>String</item-type>
|
||||
<label>Setpoint Mode</label>
|
||||
<description>Chosen setpoint_mode (program, away, hg, manual, off, max)</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="program">Following a weekly schedule</option>
|
||||
<option value="away">Applying the -away- temperature as defined by the user</option>
|
||||
<option value="hg">Frost-guard</option>
|
||||
<option value="manual">Applying a manually set temperature setpoint</option>
|
||||
<option value="off">Currently off</option>
|
||||
<option value="max">Heating continuously</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ThermRelayCmd" advanced="false">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Heating Status</label>
|
||||
<description>Indicates whether the furnace is heating or not</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ThermOrientation" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Orientation</label>
|
||||
<description>Physical orientation of the thermostat module</description>
|
||||
<state readOnly="true" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="timeUtc" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Measurement Time</label>
|
||||
<description>Timestamp when data was measured</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastPlugSeen" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Plug Seen</label>
|
||||
<description>Last Plug Seen</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinCo2" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min CO2</label>
|
||||
<description>Date when minimum CO2 was reached on current day</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinCo2ThisWeek" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min CO2 This Week</label>
|
||||
<description>Date when minimum CO2 was reached this week</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinCo2ThisMonth" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min CO2 This Month</label>
|
||||
<description>Date when minimum CO2 was reached this month</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxCo2" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max CO2</label>
|
||||
<description>Date when maximum CO2 was reached on current day</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxCo2ThisWeek" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max CO2 This Week</label>
|
||||
<description>Date when maximum CO2 was reached this week</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxCo2ThisMonth" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max CO2 This Month</label>
|
||||
<description>Date when maximum CO2 was reached this month</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinTemp" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Temp</label>
|
||||
<description>Date when minimum temperature was reached on current day</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinTempThisWeek" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Temp This Week</label>
|
||||
<description>Date when minimum temperature was reached this week</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinTempThisMonth" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Temp This Month</label>
|
||||
<description>Date when minimum temperature was reached this month</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxTemp" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Temp</label>
|
||||
<description>Date when maximum temperature was reached on current day</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxTempThisWeek" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Temp This Week</label>
|
||||
<description>Date when maximum temperature was reached this week</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxTempThisMonth" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Temp This Month</label>
|
||||
<description>Date when maximum temperature was reached this month</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinHumidity" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Humidity</label>
|
||||
<description>Date when minimum humidity was reached on current day</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinHumidityThisWeek" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Humidity This Week</label>
|
||||
<description>Date when minimum humidity was reached this week</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinHumidityThisMonth" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Humidity This Month</label>
|
||||
<description>Date when minimum humidity was reached this month</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxHumidity" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Humidity</label>
|
||||
<description>Date when maximum humidity was reached on current day</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxHumidityThisWeek" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Humidity This Week</label>
|
||||
<description>Date when maximum humidity was reached this week</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxHumidityThisMonth" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Humidity This Month</label>
|
||||
<description>Date when maximum humidity was reached this month</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinNoise" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Noise</label>
|
||||
<description>Date when minimum noise was reached on current day</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinNoiseThisWeek" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Noise This Week</label>
|
||||
<description>Date when minimum noise was reached this week</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinNoiseThisMonth" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Noise This Month</label>
|
||||
<description>Date when minimum noise was reached this month</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxNoise" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Noise</label>
|
||||
<description>Date when maximum noise was reached on current day</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxNoiseThisWeek" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Noise This Week</label>
|
||||
<description>Date when maximum noise was reached this week</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxNoiseThisMonth" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Noise This Month</label>
|
||||
<description>Date when maximum noise was reached this month</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinPressure" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Pressure</label>
|
||||
<description>Date when minimum pressure was reached on current day</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinPressureThisWeek" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Pressure This Week</label>
|
||||
<description>Date when minimum pressure was reached this week</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMinPressureThisMonth" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Min Pressure This Month</label>
|
||||
<description>Date when minimum pressure was reached this month</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxPressure" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Pressure</label>
|
||||
<description>Date when maximum pressure was reached on current day</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxPressureThisWeek" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Pressure This Week</label>
|
||||
<description>Date when maximum pressure was reached this week</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dateMaxPressureThisMonth" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Pressure This Month</label>
|
||||
<description>Date when maximum pressure was reached this month</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastBilan" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Available Bilan</label>
|
||||
<description>Month of the last available thermostat bilan</description>
|
||||
<state readOnly="true" pattern="%1$td.%1$tm.%1$tY"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="connectedBoiler" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Plug Connected Boiler</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastMessage" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Message</label>
|
||||
<description>Last Message emitted by the module</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="setpointEndTime" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Setpoint End</label>
|
||||
<description>Thermostat goes back to schedule after that timestamp.</description>
|
||||
<state readOnly="true" pattern="%1$td.%1$tm.%1$tY %1$tH:%1$tM"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastThermSeen" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Therm Seen</label>
|
||||
<description>Last Them Seen</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="co2">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>CO2</label>
|
||||
<description>Air Quality</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minCo2" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Min Co2</label>
|
||||
<description>Minimum CO2 on current day</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minCo2ThisWeek" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Min Co2 This Week</label>
|
||||
<description>Minimum CO2 this week</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minCo2ThisMonth" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Min Co2 This Month</label>
|
||||
<description>Minimum CO2 this month</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxCo2" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max Co2</label>
|
||||
<description>Maximum CO2 on current day</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxCo2ThisWeek" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max Co2 This Week</label>
|
||||
<description>Maximum CO2 this week</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxCo2ThisMonth" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max Co2 This Month</label>
|
||||
<description>Maximum CO2 this month</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="noise">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Noise</label>
|
||||
<description>Current Noise Level</description>
|
||||
<category>Noise</category>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minNoise" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Min Noise</label>
|
||||
<description>Minimum Noise on current day</description>
|
||||
<category>Noise</category>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minNoiseThisWeek" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Min Noise This Week</label>
|
||||
<description>Minimum Noise this week</description>
|
||||
<category>Noise</category>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minNoiseThisMonth" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Min Noise This Month</label>
|
||||
<description>Minimum Noise this month</description>
|
||||
<category>Noise</category>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxNoise" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max Noise</label>
|
||||
<description>Maximum Noise on current day</description>
|
||||
<category>Noise</category>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxNoiseThisWeek" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max Noise This Week</label>
|
||||
<description>Maximum Noise this week</description>
|
||||
<category>Noise</category>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxNoiseThisMonth" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max Noise This Month</label>
|
||||
<description>Maximum Noise this month</description>
|
||||
<category>Noise</category>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="healthindex">
|
||||
<item-type>String</item-type>
|
||||
<label>Health Index</label>
|
||||
<description>Health Index (healthy, fine, fair, poor, unhealthy)</description>
|
||||
<state readOnly="true" pattern="%s">
|
||||
<options>
|
||||
<option value="healthy">healthy</option>
|
||||
<option value="fine">fine</option>
|
||||
<option value="fair">fair</option>
|
||||
<option value="poor">poor</option>
|
||||
<option value="unhealthy">unhealthy</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pressure">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Pressure</label>
|
||||
<description>Current pressure</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.3f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minPressure" advanced="true">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Min Pressure</label>
|
||||
<description>Minimum Pressure on current day</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.3f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minPressureThisWeek" advanced="true">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Min Pressure This Week</label>
|
||||
<description>Minimum Pressure this week</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.3f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minPressureThisMonth" advanced="true">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Min Pressure This Month</label>
|
||||
<description>Minimum Pressure this month</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.3f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxPressure" advanced="true">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Max Pressure</label>
|
||||
<description>Maximum Pressure on current day</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.3f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxPressureThisWeek" advanced="true">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Max Pressure This Week</label>
|
||||
<description>Maximum Pressure this week</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.3f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxPressureThisMonth" advanced="true">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Max Pressure This Month</label>
|
||||
<description>Maximum Pressure this month</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.3f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pressureTrend" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Pressure Trend</label>
|
||||
<description>Pressure evolution trend for last 12h (up, down, stable)</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%s">
|
||||
<options>
|
||||
<option value="up">up</option>
|
||||
<option value="stable">stable</option>
|
||||
<option value="down">down</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="planning" advanced="false">
|
||||
<item-type>String</item-type>
|
||||
<label>Planning</label>
|
||||
<description>Heat planning currently used</description>
|
||||
<state pattern="%s"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="absolutePressure" advanced="true">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Abs Pressure</label>
|
||||
<description>Absolute pressure</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.3f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity</label>
|
||||
<description>Current humidity</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minHumidity" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Min Humidity</label>
|
||||
<description>Minimum Humidity on current day</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minHumidityThisWeek" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Min Humidity This Week</label>
|
||||
<description>Minimum Humidity this week</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="minHumidityThisMonth" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Min Humidity This Month</label>
|
||||
<description>Minimum Humidity this month</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxHumidity" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max Humidity</label>
|
||||
<description>Maximum Humidity on current day</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxHumidityThisWeek" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max Humidity This Week</label>
|
||||
<description>Minimum Humidity this week</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="maxHumidityThisMonth" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max Humidity This Month</label>
|
||||
<description>Maximum Humidity this month</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidex">
|
||||
<item-type>Number</item-type>
|
||||
<label>Humidex</label>
|
||||
<description>Computed Humidex index</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.0f"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="heatIndex">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Heat Index</label>
|
||||
<description>Computed Heat Index</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dewPoint" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Dewpoint</label>
|
||||
<description>Computed Dewpoint Temperature</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dewPointDepression" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Dewpoint Depression</label>
|
||||
<description>Computed Dewpoint Depression</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rain">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain</label>
|
||||
<description>Quantity of water</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rain1" advanced="true">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain 1h</label>
|
||||
<description>Quantity of water on last hour</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rain24" advanced="true">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain 24h</label>
|
||||
<description>Quantity of water on last day</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rainThisWeek" advanced="true">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain This Week</label>
|
||||
<description>Quantity of water this week</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rainThisMonth" advanced="true">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain This Month</label>
|
||||
<description>Quantity of water this month</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WindAngle">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Wind Angle</label>
|
||||
<description>Current 5 minutes average wind direction</description>
|
||||
<category>Wind</category>
|
||||
<state min="0" max="360" step="1" readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WindStrength">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Strength</label>
|
||||
<description>Current 5 minutes average wind speed</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="MaxWindStrength" advanced="true">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Max Wind Strength</label>
|
||||
<description>Maximum wind strength recorded</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="DateMaxWindStrength" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Max Wind Strength</label>
|
||||
<description>Timestamp when MaxWindStrength was recorded.</description>
|
||||
<state readOnly="true" pattern="%1$td.%1$tm.%1$tY %1$tH:%1$tM"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="GustAngle">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Gust Angle</label>
|
||||
<description>Direction of the last 5 minutes highest gust wind</description>
|
||||
<category>Wind</category>
|
||||
<state min="0" max="360" step="1" readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="GustStrength">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Gust Strength</label>
|
||||
<description>Speed of the last 5 minutes highest gust wind</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="homeEvent">
|
||||
<kind>trigger</kind>
|
||||
<label>Home Event</label>
|
||||
<description>Home event</description>
|
||||
<event>
|
||||
<options>
|
||||
<option value="PERSON">person</option>
|
||||
<option value="PERSON_AWAY">person_away</option>
|
||||
<option value="MOVEMENT">movement</option>
|
||||
<option value="CONNECTION">connection</option>
|
||||
<option value="DISCONNECTION">disconnection</option>
|
||||
<option value="ON">on</option>
|
||||
<option value="OFF">off</option>
|
||||
<option value="BOOT">boot</option>
|
||||
<option value="SD">sd</option>
|
||||
<option value="ALIM">alim</option>
|
||||
<option value="NEW_MODULE">new_module</option>
|
||||
<option value="MODULE_CONNECT">module_connect</option>
|
||||
<option value="MODULE_DISCONNECT">module_disconnect</option>
|
||||
<option value="MODULE_LOW_BATTERY">module_low_battery</option>
|
||||
<option value="MODULE_END_UPDATE">module_end_update</option>
|
||||
<option value="TAG_BIG_MOVE">tag_big_move</option>
|
||||
<option value="TAG_SMALL_MOVE">tag_small_move</option>
|
||||
<option value="TAG_UNINSTALLED">tag_uninstalled</option>
|
||||
<option value="TAG_OPEN">tag_open</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="netatmo"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="NHC">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Healthy Home Coach</label>
|
||||
<description>This represents the healthy home coach capable of reporting health
|
||||
index,temperature,humidity,pressure,air quality and sound level</description>
|
||||
<channels>
|
||||
<channel id="HealthIndex" typeId="healthindex"/>
|
||||
<channel id="Co2" typeId="co2"/>
|
||||
<channel id="Temperature" typeId="temperature"/>
|
||||
<channel id="TempTrend" typeId="temperatureTrend"/>
|
||||
<channel id="Noise" typeId="noise"/>
|
||||
<channel id="Pressure" typeId="pressure"/>
|
||||
<channel id="PressTrend" typeId="pressureTrend"/>
|
||||
<channel id="AbsolutePressure" typeId="absolutePressure"/>
|
||||
<channel id="TimeStamp" typeId="timeUtc"/>
|
||||
<channel id="Humidity" typeId="humidity"/>
|
||||
<channel id="MinTemp" typeId="minTemp"/>
|
||||
<channel id="MaxTemp" typeId="maxTemp"/>
|
||||
<channel id="DateMinTemp" typeId="dateMinTemp"/>
|
||||
<channel id="DateMaxTemp" typeId="dateMaxTemp"/>
|
||||
<!-- Common to all devices -->
|
||||
<channel id="LastStatusStore" typeId="lastStatusStore"/>
|
||||
<channel id="WifiStatus" typeId="system.signal-strength"/>
|
||||
<channel id="Location" typeId="location"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="signalLevels">86,71,56</property>
|
||||
<property name="refreshPeriod">auto</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:station"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,295 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="netatmo"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="NAMain">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Main Indoor Station</label>
|
||||
<description>This represents the main indoor module capable of reporting temperature,humidity,pressure,air quality and
|
||||
sound level</description>
|
||||
|
||||
<channels>
|
||||
<channel id="Co2" typeId="co2"/>
|
||||
<channel id="MinCo2" typeId="minCo2"/>
|
||||
<channel id="MinCo2ThisWeek" typeId="minCo2ThisWeek"/>
|
||||
<channel id="MinCo2ThisMonth" typeId="minCo2ThisMonth"/>
|
||||
<channel id="MaxCo2" typeId="maxCo2"/>
|
||||
<channel id="MaxCo2ThisWeek" typeId="maxCo2ThisWeek"/>
|
||||
<channel id="MaxCo2ThisMonth" typeId="maxCo2ThisMonth"/>
|
||||
<channel id="DateMinCo2" typeId="dateMinCo2"/>
|
||||
<channel id="DateMinCo2ThisWeek" typeId="dateMinCo2ThisWeek"/>
|
||||
<channel id="DateMinCo2ThisMonth" typeId="dateMinCo2ThisMonth"/>
|
||||
<channel id="DateMaxCo2" typeId="dateMaxCo2"/>
|
||||
<channel id="DateMaxCo2ThisWeek" typeId="dateMaxCo2ThisWeek"/>
|
||||
<channel id="DateMaxCo2ThisMonth" typeId="dateMaxCo2ThisMonth"/>
|
||||
<channel id="Temperature" typeId="temperature"/>
|
||||
<channel id="TempTrend" typeId="temperatureTrend"/>
|
||||
<channel id="Noise" typeId="noise"/>
|
||||
<channel id="MinNoise" typeId="minNoise"/>
|
||||
<channel id="MinNoiseThisWeek" typeId="minNoiseThisWeek"/>
|
||||
<channel id="MinNoiseThisMonth" typeId="minNoiseThisMonth"/>
|
||||
<channel id="MaxNoise" typeId="maxNoise"/>
|
||||
<channel id="MaxNoiseThisWeek" typeId="maxNoiseThisWeek"/>
|
||||
<channel id="MaxNoiseThisMonth" typeId="maxNoiseThisMonth"/>
|
||||
<channel id="DateMinNoise" typeId="dateMinNoise"/>
|
||||
<channel id="DateMinNoiseThisWeek" typeId="dateMinNoiseThisWeek"/>
|
||||
<channel id="DateMinNoiseThisMonth" typeId="dateMinNoiseThisMonth"/>
|
||||
<channel id="DateMaxNoise" typeId="dateMaxNoise"/>
|
||||
<channel id="DateMaxNoiseThisWeek" typeId="dateMaxNoiseThisWeek"/>
|
||||
<channel id="DateMaxNoiseThisMonth" typeId="dateMaxNoiseThisMonth"/>
|
||||
<channel id="Pressure" typeId="pressure"/>
|
||||
<channel id="MinPressure" typeId="minPressure"/>
|
||||
<channel id="MinPressureThisWeek" typeId="minPressureThisWeek"/>
|
||||
<channel id="MinPressureThisMonth" typeId="minPressureThisMonth"/>
|
||||
<channel id="MaxPressure" typeId="maxPressure"/>
|
||||
<channel id="MaxPressureThisWeek" typeId="maxPressureThisWeek"/>
|
||||
<channel id="MaxPressureThisMonth" typeId="maxPressureThisMonth"/>
|
||||
<channel id="DateMinPressure" typeId="dateMinPressure"/>
|
||||
<channel id="DateMinPressureThisWeek" typeId="dateMinPressureThisWeek"/>
|
||||
<channel id="DateMinPressureThisMonth" typeId="dateMinPressureThisMonth"/>
|
||||
<channel id="DateMaxPressure" typeId="dateMaxPressure"/>
|
||||
<channel id="DateMaxPressureThisWeek" typeId="dateMaxPressureThisWeek"/>
|
||||
<channel id="DateMaxPressureThisMonth" typeId="dateMaxPressureThisMonth"/>
|
||||
<channel id="PressTrend" typeId="pressureTrend"/>
|
||||
<channel id="AbsolutePressure" typeId="absolutePressure"/>
|
||||
<channel id="Humidity" typeId="humidity"/>
|
||||
<channel id="MinHumidity" typeId="minHumidity"/>
|
||||
<channel id="MinHumidityThisWeek" typeId="minHumidityThisWeek"/>
|
||||
<channel id="MinHumidityThisMonth" typeId="minHumidityThisMonth"/>
|
||||
<channel id="MaxHumidity" typeId="maxHumidity"/>
|
||||
<channel id="MaxHumidityThisWeek" typeId="maxHumidityThisWeek"/>
|
||||
<channel id="MaxHumidityThisMonth" typeId="maxHumidityThisMonth"/>
|
||||
<channel id="DateMinHumidity" typeId="dateMinHumidity"/>
|
||||
<channel id="DateMinHumidityThisWeek" typeId="dateMinHumidityThisWeek"/>
|
||||
<channel id="DateMinHumidityThisMonth" typeId="dateMinHumidityThisMonth"/>
|
||||
<channel id="DateMaxHumidity" typeId="dateMaxHumidity"/>
|
||||
<channel id="DateMaxHumidityThisWeek" typeId="dateMaxHumidityThisWeek"/>
|
||||
<channel id="DateMaxHumidityThisMonth" typeId="dateMaxHumidityThisMonth"/>
|
||||
<channel id="Humidex" typeId="humidex"/>
|
||||
<channel id="HeatIndex" typeId="heatIndex"/>
|
||||
<channel id="Dewpoint" typeId="dewPoint"/>
|
||||
<channel id="DewpointDepression" typeId="dewPointDepression"/>
|
||||
<channel id="MinTemp" typeId="minTemp"/>
|
||||
<channel id="MinTempThisWeek" typeId="minTempThisWeek"/>
|
||||
<channel id="MinTempThisMonth" typeId="minTempThisMonth"/>
|
||||
<channel id="MaxTemp" typeId="maxTemp"/>
|
||||
<channel id="MaxTempThisWeek" typeId="maxTempThisWeek"/>
|
||||
<channel id="MaxTempThisMonth" typeId="maxTempThisMonth"/>
|
||||
<channel id="DateMinTemp" typeId="dateMinTemp"/>
|
||||
<channel id="DateMinTempThisWeek" typeId="dateMinTempThisWeek"/>
|
||||
<channel id="DateMinTempThisMonth" typeId="dateMinTempThisMonth"/>
|
||||
<channel id="DateMaxTemp" typeId="dateMaxTemp"/>
|
||||
<channel id="DateMaxTempThisWeek" typeId="dateMaxTempThisWeek"/>
|
||||
<channel id="DateMaxTempThisMonth" typeId="dateMaxTempThisMonth"/>
|
||||
<!-- Common to all devices -->
|
||||
<channel id="TimeStamp" typeId="timeUtc"/>
|
||||
<channel id="LastStatusStore" typeId="lastStatusStore"/>
|
||||
<channel id="WifiStatus" typeId="system.signal-strength"/>
|
||||
<channel id="Location" typeId="location"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="signalLevels">86,71,56</property>
|
||||
<property name="refreshPeriod">auto</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:station"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="NAModule1">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Outdoor Module</label>
|
||||
<description>This represents the outdoor module capable of reporting temperature and humidity</description>
|
||||
|
||||
<channels>
|
||||
<channel id="Temperature" typeId="temperature"/>
|
||||
<channel id="TempTrend" typeId="temperatureTrend"/>
|
||||
<channel id="Humidity" typeId="humidity"/>
|
||||
<channel id="MinHumidity" typeId="minHumidity"/>
|
||||
<channel id="MinHumidityThisWeek" typeId="minHumidityThisWeek"/>
|
||||
<channel id="MinHumidityThisMonth" typeId="minHumidityThisMonth"/>
|
||||
<channel id="MaxHumidity" typeId="maxHumidity"/>
|
||||
<channel id="MaxHumidityThisWeek" typeId="maxHumidityThisWeek"/>
|
||||
<channel id="MaxHumidityThisMonth" typeId="maxHumidityThisMonth"/>
|
||||
<channel id="DateMinHumidity" typeId="dateMinHumidity"/>
|
||||
<channel id="DateMinHumidityThisWeek" typeId="dateMinHumidityThisWeek"/>
|
||||
<channel id="DateMinHumidityThisMonth" typeId="dateMinHumidityThisMonth"/>
|
||||
<channel id="DateMaxHumidity" typeId="dateMaxHumidity"/>
|
||||
<channel id="DateMaxHumidityThisWeek" typeId="dateMaxHumidityThisWeek"/>
|
||||
<channel id="DateMaxHumidityThisMonth" typeId="dateMaxHumidityThisMonth"/>
|
||||
<channel id="Humidex" typeId="humidex"/>
|
||||
<channel id="HeatIndex" typeId="heatIndex"/>
|
||||
<channel id="Dewpoint" typeId="dewPoint"/>
|
||||
<channel id="DewpointDepression" typeId="dewPointDepression"/>
|
||||
<channel id="MinTemp" typeId="minTemp"/>
|
||||
<channel id="MinTempThisWeek" typeId="minTempThisWeek"/>
|
||||
<channel id="MinTempThisMonth" typeId="minTempThisMonth"/>
|
||||
<channel id="MaxTemp" typeId="maxTemp"/>
|
||||
<channel id="MaxTempThisWeek" typeId="maxTempThisWeek"/>
|
||||
<channel id="MaxTempThisMonth" typeId="maxTempThisMonth"/>
|
||||
<channel id="DateMinTemp" typeId="dateMinTemp"/>
|
||||
<channel id="DateMinTempThisWeek" typeId="dateMinTempThisWeek"/>
|
||||
<channel id="DateMinTempThisMonth" typeId="dateMinTempThisMonth"/>
|
||||
<channel id="DateMaxTemp" typeId="dateMaxTemp"/>
|
||||
<channel id="DateMaxTempThisWeek" typeId="dateMaxTempThisWeek"/>
|
||||
<channel id="DateMaxTempThisMonth" typeId="dateMaxTempThisMonth"/>
|
||||
<!-- Common to all modules -->
|
||||
<channel id="TimeStamp" typeId="timeUtc"/>
|
||||
<channel id="LastMessage" typeId="lastMessage"/>
|
||||
<channel id="LowBattery" typeId="system.low-battery"/>
|
||||
<channel id="BatteryVP" typeId="system.battery-level"/>
|
||||
<channel id="RfStatus" typeId="system.signal-strength"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="signalLevels">90,80,70,60</property>
|
||||
<property name="batteryLevels">3600,4500,6000</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:module"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="NAModule2">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Wind Gauge Module</label>
|
||||
<description>This represents the wind module capable of reporting wind angle and strength</description>
|
||||
|
||||
<channels>
|
||||
<channel id="WindAngle" typeId="WindAngle"/>
|
||||
<channel id="WindStrength" typeId="WindStrength"/>
|
||||
<channel id="MaxWindStrength" typeId="MaxWindStrength"/>
|
||||
<channel id="DateMaxWindStrength" typeId="DateMaxWindStrength"/>
|
||||
<channel id="GustAngle" typeId="GustAngle"/>
|
||||
<channel id="GustStrength" typeId="GustStrength"/>
|
||||
<!-- Common to all modules -->
|
||||
<channel id="TimeStamp" typeId="timeUtc"/>
|
||||
<channel id="LastMessage" typeId="lastMessage"/>
|
||||
<channel id="LowBattery" typeId="system.low-battery"/>
|
||||
<channel id="BatteryVP" typeId="system.battery-level"/>
|
||||
<channel id="RfStatus" typeId="system.signal-strength"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="signalLevels">90,80,70,60</property>
|
||||
<property name="batteryLevels">3950,4770,6000</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:module"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="NAModule3">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Rain Gauge</label>
|
||||
<description>This represents the Rain Gauge capable of measuring precipitation</description>
|
||||
|
||||
<channels>
|
||||
<channel id="Rain" typeId="rain"/>
|
||||
<channel id="SumRain1" typeId="rain1"/>
|
||||
<channel id="SumRain24" typeId="rain24"/>
|
||||
<channel id="SumRainThisWeek" typeId="rainThisWeek"/>
|
||||
<channel id="SumRainThisMonth" typeId="rainThisMonth"/>
|
||||
<!-- Common to all modules -->
|
||||
<channel id="TimeStamp" typeId="timeUtc"/>
|
||||
<channel id="LastMessage" typeId="lastMessage"/>
|
||||
<channel id="LowBattery" typeId="system.low-battery"/>
|
||||
<channel id="BatteryVP" typeId="system.battery-level"/>
|
||||
<channel id="RfStatus" typeId="system.signal-strength"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="signalLevels">90,80,70,60</property>
|
||||
<property name="batteryLevels">3600,4500,6000</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:module"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="NAModule4">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Additional Module</label>
|
||||
<description>This represents an additional indoor module capable of reporting temperature, humidity and CO2 level</description>
|
||||
|
||||
<channels>
|
||||
<channel id="Co2" typeId="co2"/>
|
||||
<channel id="MinCo2" typeId="minCo2"/>
|
||||
<channel id="MinCo2ThisWeek" typeId="minCo2ThisWeek"/>
|
||||
<channel id="MinCo2ThisMonth" typeId="minCo2ThisMonth"/>
|
||||
<channel id="MaxCo2" typeId="maxCo2"/>
|
||||
<channel id="MaxCo2ThisWeek" typeId="maxCo2ThisWeek"/>
|
||||
<channel id="MaxCo2ThisMonth" typeId="maxCo2ThisMonth"/>
|
||||
<channel id="DateMinCo2" typeId="dateMinCo2"/>
|
||||
<channel id="DateMinCo2ThisWeek" typeId="dateMinCo2ThisWeek"/>
|
||||
<channel id="DateMinCo2ThisMonth" typeId="dateMinCo2ThisMonth"/>
|
||||
<channel id="DateMaxCo2" typeId="dateMaxCo2"/>
|
||||
<channel id="DateMaxCo2ThisWeek" typeId="dateMaxCo2ThisWeek"/>
|
||||
<channel id="DateMaxCo2ThisMonth" typeId="dateMaxCo2ThisMonth"/>
|
||||
<channel id="Temperature" typeId="temperature"/>
|
||||
<channel id="TempTrend" typeId="temperatureTrend"/>
|
||||
<channel id="Humidity" typeId="humidity"/>
|
||||
<channel id="MinHumidity" typeId="minHumidity"/>
|
||||
<channel id="MinHumidityThisWeek" typeId="minHumidityThisWeek"/>
|
||||
<channel id="MinHumidityThisMonth" typeId="minHumidityThisMonth"/>
|
||||
<channel id="MaxHumidity" typeId="maxHumidity"/>
|
||||
<channel id="MaxHumidityThisWeek" typeId="maxHumidityThisWeek"/>
|
||||
<channel id="MaxHumidityThisMonth" typeId="maxHumidityThisMonth"/>
|
||||
<channel id="DateMinHumidity" typeId="dateMinHumidity"/>
|
||||
<channel id="DateMinHumidityThisWeek" typeId="dateMinHumidityThisWeek"/>
|
||||
<channel id="DateMinHumidityThisMonth" typeId="dateMinHumidityThisMonth"/>
|
||||
<channel id="DateMaxHumidity" typeId="dateMaxHumidity"/>
|
||||
<channel id="DateMaxHumidityThisWeek" typeId="dateMaxHumidityThisWeek"/>
|
||||
<channel id="DateMaxHumidityThisMonth" typeId="dateMaxHumidityThisMonth"/>
|
||||
<channel id="Humidex" typeId="humidex"/>
|
||||
<channel id="HeatIndex" typeId="heatIndex"/>
|
||||
<channel id="Dewpoint" typeId="dewPoint"/>
|
||||
<channel id="DewpointDepression" typeId="dewPointDepression"/>
|
||||
<channel id="MinTemp" typeId="minTemp"/>
|
||||
<channel id="MinTempThisWeek" typeId="minTempThisWeek"/>
|
||||
<channel id="MinTempThisMonth" typeId="minTempThisMonth"/>
|
||||
<channel id="MaxTemp" typeId="maxTemp"/>
|
||||
<channel id="MaxTempThisWeek" typeId="maxTempThisWeek"/>
|
||||
<channel id="MaxTempThisMonth" typeId="maxTempThisMonth"/>
|
||||
<channel id="DateMinTemp" typeId="dateMinTemp"/>
|
||||
<channel id="DateMinTempThisWeek" typeId="dateMinTempThisWeek"/>
|
||||
<channel id="DateMinTempThisMonth" typeId="dateMinTempThisMonth"/>
|
||||
<channel id="DateMaxTemp" typeId="dateMaxTemp"/>
|
||||
<channel id="DateMaxTempThisWeek" typeId="dateMaxTempThisWeek"/>
|
||||
<channel id="DateMaxTempThisMonth" typeId="dateMaxTempThisMonth"/>
|
||||
<!-- Common to all modules -->
|
||||
<channel id="TimeStamp" typeId="timeUtc"/>
|
||||
<channel id="LastMessage" typeId="lastMessage"/>
|
||||
<channel id="LowBattery" typeId="system.low-battery"/>
|
||||
<channel id="BatteryVP" typeId="system.battery-level"/>
|
||||
<channel id="RfStatus" typeId="system.signal-strength"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="signalLevels">90,80,70,60</property>
|
||||
<property name="batteryLevels">4200,4920,6000</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:module"/>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="netatmo"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- Netatmo Thermostat and Relay/Plug -->
|
||||
<thing-type id="NAPlug">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Thermostat Relay/Plug</label>
|
||||
<description>This represents the thermostat relay</description>
|
||||
|
||||
<channels>
|
||||
<channel id="ConnectedBoiler" typeId="connectedBoiler"/>
|
||||
<channel id="LastPlugSeen" typeId="lastPlugSeen"/>
|
||||
<channel id="LastBilan" typeId="lastBilan"/>
|
||||
<!-- Common to all devices -->
|
||||
<channel id="LastStatusStore" typeId="lastStatusStore"/>
|
||||
<channel id="WifiStatus" typeId="system.signal-strength"/>
|
||||
<channel id="Location" typeId="location"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="signalLevels">86,71,56</property>
|
||||
<property name="refreshPeriod">3600000</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:plug"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="NATherm1">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Thermostat Module</label>
|
||||
<description>This represents the thermostat module itself</description>
|
||||
|
||||
<channels>
|
||||
<channel id="Temperature" typeId="temperature"/>
|
||||
<channel id="Sp_Temperature" typeId="setpointTemp"/>
|
||||
<channel id="SetpointMode" typeId="setpointMode"/>
|
||||
<channel id="ThermRelayCmd" typeId="ThermRelayCmd"/>
|
||||
<channel id="ThermOrientation" typeId="ThermOrientation"/>
|
||||
<channel id="TimeStamp" typeId="timeUtc"/>
|
||||
<channel id="Planning" typeId="planning"/>
|
||||
<channel id="SetpointEndTime" typeId="setpointEndTime"/>
|
||||
|
||||
<!-- Common to all modules -->
|
||||
<channel id="LastMessage" typeId="lastMessage"/>
|
||||
<channel id="LowBattery" typeId="system.low-battery"/>
|
||||
<channel id="BatteryVP" typeId="system.battery-level"/>
|
||||
<channel id="RfStatus" typeId="system.signal-strength"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="signalLevels">90,80,70,60</property>
|
||||
<property name="batteryLevels">2700,3300,4500</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:natherm1"/>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="netatmo"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="NAWelcomeHome">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Welcome Home</label>
|
||||
<description>This represents a home hosting a camera</description>
|
||||
|
||||
<channels>
|
||||
<channel id="welcomeHomeCity" typeId="homecity"></channel>
|
||||
<channel id="welcomeHomeCountry" typeId="homecountry"></channel>
|
||||
<channel id="welcomeHomeTimezone" typeId="hometimezone"></channel>
|
||||
|
||||
<channel id="welcomeHomePersonCount" typeId="homepersoncount"></channel>
|
||||
<channel id="welcomeHomeUnknownCount" typeId="homeunknowncount"></channel>
|
||||
|
||||
<channel id="welcomeEventType" typeId="type"></channel>
|
||||
<channel id="welcomeEventTime" typeId="time"></channel>
|
||||
<channel id="welcomeEventCameraId" typeId="camera_id"></channel>
|
||||
<channel id="welcomeEventPersonId" typeId="person_id"></channel>
|
||||
<channel id="welcomeEventSnapshot" typeId="snapshot"></channel>
|
||||
<channel id="welcomeEventSnapshotURL" typeId="snapshot_url"></channel>
|
||||
<channel id="welcomeEventVideoURL" typeId="video_url"></channel>
|
||||
<channel id="welcomeEventVideoStatus" typeId="video_status"></channel>
|
||||
<channel id="welcomeEventIsArrival" typeId="is_arrival"></channel>
|
||||
<channel id="welcomeEventMessage" typeId="message"></channel>
|
||||
<channel id="welcomeEventSubType" typeId="sub_type"></channel>
|
||||
<channel id="welcomeHomeEvent" typeId="homeEvent"></channel>
|
||||
|
||||
<channel id="cameraEvent" typeId="cameraEvent"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:welcomehome"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="homecity">
|
||||
<item-type>String</item-type>
|
||||
<label>City</label>
|
||||
<description>City of the home</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="homecountry">
|
||||
<item-type>String</item-type>
|
||||
<label>Country</label>
|
||||
<description>Country of the home</description>
|
||||
<state readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="hometimezone">
|
||||
<item-type>String</item-type>
|
||||
<label>Timezone</label>
|
||||
<description>Timezone of the home</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="homepersoncount" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>Person Counter</label>
|
||||
<description>Total number of Persons that are at home</description>
|
||||
<state readOnly="true" pattern="%d"></state>
|
||||
</channel-type>
|
||||
<channel-type id="homeunknowncount" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Unknown Person Counter</label>
|
||||
<description>Count how many Unknown Persons are at home</description>
|
||||
<state readOnly="true" pattern="%d"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="type">
|
||||
<item-type>String</item-type>
|
||||
<label>Type</label>
|
||||
<description>Type of event. Go to the Welcome page for further details.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="time">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Time</label>
|
||||
<description>Time of occurrence of event</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="camera_id">
|
||||
<item-type>String</item-type>
|
||||
<label>Camera ID</label>
|
||||
<description>Camera that detected the event</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="person_id">
|
||||
<item-type>String</item-type>
|
||||
<label>Person ID</label>
|
||||
<description>Id of the person the event is about (if any)</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="snapshot_url" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Snapshot URL</label>
|
||||
<description>Url of the event snapshot</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="snapshot">
|
||||
<item-type>Image</item-type>
|
||||
<label>Event Snapshot</label>
|
||||
<description>Event Snapshot</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="video_url" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Video URL</label>
|
||||
<description>URL of the event video</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="video_status">
|
||||
<item-type>String</item-type>
|
||||
<label>Video Status</label>
|
||||
<description>Status of the video (recording, deleted or available)</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="is_arrival">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Is Arrival</label>
|
||||
<description>If person was considered "away" before being seen during this event</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="message">
|
||||
<item-type>String</item-type>
|
||||
<label>Message</label>
|
||||
<description>Message sent by Netatmo corresponding to given event</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="sub_type" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Sub Type</label>
|
||||
<description>Sub-type of SD and Alim events. Go to Welcome page for further details.</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cameraEvent">
|
||||
<kind>trigger</kind>
|
||||
<label>Camera Event</label>
|
||||
<event>
|
||||
<options>
|
||||
<option value="ANIMAL">Animal detected</option>
|
||||
<option value="HUMAN">Human detected</option>
|
||||
<option value="MOVEMENT">Unspecified movement detected</option>
|
||||
<option value="VEHICLE">Vehicle detected</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="netatmo"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="NAWelcomePerson">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="netatmoapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Welcome Person</label>
|
||||
<description>This represents a person at home</description>
|
||||
|
||||
<channels>
|
||||
<channel id="welcomePersonLastSeen" typeId="last_seen"></channel>
|
||||
<channel id="welcomePersonAtHome" typeId="person_athome"></channel>
|
||||
<channel id="welcomePersonAvatarUrl" typeId="person_avatar_url"></channel>
|
||||
<channel id="welcomePersonAvatar" typeId="person_avatar"></channel>
|
||||
<channel id="welcomePersonLastEventMessage" typeId="person_eventmsg"></channel>
|
||||
<channel id="welcomePersonLastEventTime" typeId="person_eventtime"></channel>
|
||||
<channel id="welcomePersonLastEventUrl" typeId="person_event_url"></channel>
|
||||
<channel id="welcomePersonLastEvent" typeId="person_event"></channel>
|
||||
<channel id="welcomeHomeEvent" typeId="homeEvent"></channel>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
<config-description-ref uri="thing-type:netatmo:nawelcomeperson"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="last_seen">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Seen</label>
|
||||
<description>Time when this person was last seen</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="person_athome">
|
||||
<item-type>Switch</item-type>
|
||||
<label>At Home</label>
|
||||
<description>Indicates if this person is known to be at home or not</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="person_eventmsg">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Event Message</label>
|
||||
<description>Last Event message from this person</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="person_eventtime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Event Time</label>
|
||||
<description>Last Event message time for this person</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="person_avatar_url" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Avatar URL</label>
|
||||
<description>URL for the avatar of this person</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="person_avatar">
|
||||
<item-type>Image</item-type>
|
||||
<label>Avatar</label>
|
||||
<description>Avatar of this person</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="person_event">
|
||||
<item-type>Image</item-type>
|
||||
<label>Last Event Picture</label>
|
||||
<description>Picture of the last event for this person</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="person_event_url" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Event URL</label>
|
||||
<description>URL for the picture of the last event for this person</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.discovery;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
|
||||
|
||||
import io.swagger.client.model.NAMain;
|
||||
import io.swagger.client.model.NAStationDataBody;
|
||||
import io.swagger.client.model.NAStationModule;
|
||||
|
||||
/**
|
||||
* @author Sven Strohschein - Initial contribution
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class NetatmoModuleDiscoveryServiceTest {
|
||||
|
||||
private NetatmoModuleDiscoveryServiceAccessible service;
|
||||
private NetatmoBridgeHandler bridgeHandlerSpy;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
Bridge bridgeMock = mock(Bridge.class);
|
||||
when(bridgeMock.getUID()).thenReturn(new ThingUID("netatmo", "bridge"));
|
||||
|
||||
bridgeHandlerSpy = spy(new NetatmoBridgeHandler(bridgeMock, null));
|
||||
|
||||
LocaleProvider localeProviderMock = mock(LocaleProvider.class);
|
||||
TranslationProvider translationProvider = mock(TranslationProvider.class);
|
||||
|
||||
service = new NetatmoModuleDiscoveryServiceAccessible(bridgeHandlerSpy, localeProviderMock,
|
||||
translationProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanNothingActivated() {
|
||||
service.startScan();
|
||||
|
||||
assertEquals(0, service.getDiscoveredThings().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanDiscoverWeatherStationNoStationsBody() {
|
||||
activateDiscoveryWeatherStation();
|
||||
|
||||
service.startScan();
|
||||
|
||||
assertEquals(0, service.getDiscoveredThings().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanDiscoverWeatherStationNoStations() {
|
||||
activateDiscoveryWeatherStation();
|
||||
|
||||
when(bridgeHandlerSpy.getStationsDataBody(null)).thenReturn(Optional.of(new NAStationDataBody()));
|
||||
service.startScan();
|
||||
|
||||
assertEquals(0, service.getDiscoveredThings().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanDiscoverWeatherStationNoStationName() {
|
||||
recordStationBody(createStation());
|
||||
|
||||
service.startScan();
|
||||
|
||||
List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
|
||||
assertEquals(1, discoveredThings.size());
|
||||
// Expected is only the type name, because a station name isn't available
|
||||
assertEquals("NAMain", discoveredThings.get(0).getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanDiscoverWeatherStation() {
|
||||
NAMain station = createStation();
|
||||
station.setStationName("Neu Wulmstorf");
|
||||
|
||||
recordStationBody(station);
|
||||
|
||||
service.startScan();
|
||||
|
||||
List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
|
||||
assertEquals(1, discoveredThings.size());
|
||||
// Expected is the type name + station name, because both are available
|
||||
// and the station name contains only the city name by default which wouldn't be sufficient.
|
||||
assertEquals("NAMain Neu Wulmstorf", discoveredThings.get(0).getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanDiscoverWeatherStationNoStationNameFavorite() {
|
||||
NAMain station = createStation();
|
||||
station.setFavorite(true);
|
||||
|
||||
recordStationBody(station);
|
||||
|
||||
service.startScan();
|
||||
|
||||
List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
|
||||
assertEquals(1, discoveredThings.size());
|
||||
// Expected is "(favorite)" within the label to make clear that it is favorite station
|
||||
// (and not the station of the user)
|
||||
assertEquals("NAMain (favorite)", discoveredThings.get(0).getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanDiscoverWeatherStationFavorite() {
|
||||
NAMain station = createStation();
|
||||
station.setStationName("Neu Wulmstorf");
|
||||
station.setFavorite(true);
|
||||
|
||||
recordStationBody(station);
|
||||
|
||||
service.startScan();
|
||||
|
||||
List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
|
||||
assertEquals(1, discoveredThings.size());
|
||||
// Expected is "(favorite)" within the label to make clear that it is favorite station
|
||||
// (and not the station of the user)
|
||||
assertEquals("NAMain Neu Wulmstorf (favorite)", discoveredThings.get(0).getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanDiscoverWeatherStationModuleNoModuleName() {
|
||||
NAMain station = createStation(createModule());
|
||||
station.setStationName("Neu Wulmstorf");
|
||||
|
||||
recordStationBody(station);
|
||||
|
||||
service.startScan();
|
||||
|
||||
List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
|
||||
assertEquals(2, discoveredThings.size());
|
||||
assertEquals("NAMain Neu Wulmstorf", discoveredThings.get(0).getLabel());
|
||||
// Expected is the type name + station name to make clear that the module belongs to the station.
|
||||
// The module name isn't available, therefore the type name of the module is used.
|
||||
assertEquals("NAModule1 Neu Wulmstorf", discoveredThings.get(1).getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanDiscoverWeatherStationModule() {
|
||||
NAStationModule module = createModule();
|
||||
module.setModuleName("Outdoor-Module");
|
||||
|
||||
NAMain station = createStation(module);
|
||||
station.setStationName("Neu Wulmstorf");
|
||||
|
||||
recordStationBody(station);
|
||||
|
||||
service.startScan();
|
||||
|
||||
List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
|
||||
assertEquals(2, discoveredThings.size());
|
||||
assertEquals("NAMain Neu Wulmstorf", discoveredThings.get(0).getLabel());
|
||||
// Expected is the module name + station name to make clear that the module belongs to the station.
|
||||
// Because an explicit module name is available, the module type name isn't required.
|
||||
assertEquals("Outdoor-Module Neu Wulmstorf", discoveredThings.get(1).getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanDiscoverWeatherStationModuleNoModuleNameFavorite() {
|
||||
NAMain station = createStation(createModule());
|
||||
station.setStationName("Neu Wulmstorf");
|
||||
station.setFavorite(true);
|
||||
|
||||
recordStationBody(station);
|
||||
|
||||
service.startScan();
|
||||
|
||||
List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
|
||||
assertEquals(2, discoveredThings.size());
|
||||
assertEquals("NAMain Neu Wulmstorf (favorite)", discoveredThings.get(0).getLabel());
|
||||
// Expected is "(favorite)" within the label to make clear that it is favorite station
|
||||
// (and not the station of the user)
|
||||
assertEquals("NAModule1 Neu Wulmstorf (favorite)", discoveredThings.get(1).getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartScanDiscoverWeatherStationModuleFavorite() {
|
||||
NAStationModule module = createModule();
|
||||
module.setModuleName("Outdoor-Module");
|
||||
|
||||
NAMain station = createStation(module);
|
||||
station.setStationName("Neu Wulmstorf");
|
||||
station.setFavorite(true);
|
||||
|
||||
recordStationBody(station);
|
||||
|
||||
service.startScan();
|
||||
|
||||
List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
|
||||
assertEquals(2, discoveredThings.size());
|
||||
assertEquals("NAMain Neu Wulmstorf (favorite)", discoveredThings.get(0).getLabel());
|
||||
// Expected is "(favorite)" within the label to make clear that it is favorite station
|
||||
// (and not the station of the user)
|
||||
assertEquals("Outdoor-Module Neu Wulmstorf (favorite)", discoveredThings.get(1).getLabel());
|
||||
}
|
||||
|
||||
private void recordStationBody(NAMain station) {
|
||||
activateDiscoveryWeatherStation();
|
||||
|
||||
NAStationDataBody stationsBody = new NAStationDataBody();
|
||||
stationsBody.setDevices(Collections.singletonList(station));
|
||||
|
||||
when(bridgeHandlerSpy.getStationsDataBody(null)).thenReturn(Optional.of(stationsBody));
|
||||
}
|
||||
|
||||
private void activateDiscoveryWeatherStation() {
|
||||
bridgeHandlerSpy.configuration.readStation = true;
|
||||
}
|
||||
|
||||
private static NAMain createStation() {
|
||||
NAMain station = new NAMain();
|
||||
station.setId("01:00:00:00:00:aa");
|
||||
station.setType("NAMain");
|
||||
return station;
|
||||
}
|
||||
|
||||
private static NAMain createStation(NAStationModule module) {
|
||||
NAMain station = createStation();
|
||||
station.setModules(Collections.singletonList(module));
|
||||
return station;
|
||||
}
|
||||
|
||||
private static NAStationModule createModule() {
|
||||
NAStationModule module = new NAStationModule();
|
||||
module.setId("01:00:00:00:01:aa");
|
||||
module.setType("NAModule1");
|
||||
return module;
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
private static class NetatmoModuleDiscoveryServiceAccessible extends NetatmoModuleDiscoveryService {
|
||||
|
||||
private final List<DiscoveryResult> discoveredThings;
|
||||
|
||||
private NetatmoModuleDiscoveryServiceAccessible(NetatmoBridgeHandler netatmoBridgeHandler,
|
||||
LocaleProvider localeProvider, TranslationProvider translationProvider) {
|
||||
super(netatmoBridgeHandler, localeProvider, translationProvider);
|
||||
discoveredThings = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thingDiscovered(DiscoveryResult discoveryResult) {
|
||||
super.thingDiscovered(discoveryResult);
|
||||
discoveredThings.add(discoveryResult);
|
||||
}
|
||||
|
||||
private List<DiscoveryResult> getDiscoveredThings() {
|
||||
return discoveredThings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.presence;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.internal.ThingImpl;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.openhab.binding.netatmo.internal.NetatmoBindingConstants;
|
||||
|
||||
import io.swagger.client.model.NAWelcomeCamera;
|
||||
|
||||
/**
|
||||
* @author Sven Strohschein - Initial contribution
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class NAPresenceCameraHandlerTest {
|
||||
|
||||
private static final String DUMMY_VPN_URL = "https://dummytestvpnaddress.net/restricted/10.255.89.96/9826069dc689e8327ac3ed2ced4ff089/MTU5MTgzMzYwMDrQ7eHHhG0_OJ4TgmPhGlnK7QQ5pZ,,";
|
||||
private static final String DUMMY_LOCAL_URL = "http://192.168.178.76/9826069dc689e8327ac3ed2ced4ff089";
|
||||
private static final Optional<String> DUMMY_PING_RESPONSE = createPingResponseContent(DUMMY_LOCAL_URL);
|
||||
|
||||
@Mock
|
||||
private RequestExecutor requestExecutorMock;
|
||||
@Mock
|
||||
private TimeZoneProvider timeZoneProviderMock;
|
||||
|
||||
private Thing presenceCameraThing;
|
||||
private NAWelcomeCamera presenceCamera;
|
||||
private ChannelUID cameraStatusChannelUID;
|
||||
private ChannelUID floodlightChannelUID;
|
||||
private ChannelUID floodlightAutoModeChannelUID;
|
||||
private NAPresenceCameraHandlerAccessible handler;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
presenceCameraThing = new ThingImpl(new ThingTypeUID("netatmo", "NOC"), "1");
|
||||
presenceCamera = new NAWelcomeCamera();
|
||||
|
||||
cameraStatusChannelUID = new ChannelUID(presenceCameraThing.getUID(),
|
||||
NetatmoBindingConstants.CHANNEL_CAMERA_STATUS);
|
||||
floodlightChannelUID = new ChannelUID(presenceCameraThing.getUID(),
|
||||
NetatmoBindingConstants.CHANNEL_CAMERA_FLOODLIGHT);
|
||||
floodlightAutoModeChannelUID = new ChannelUID(presenceCameraThing.getUID(),
|
||||
NetatmoBindingConstants.CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE);
|
||||
|
||||
handler = new NAPresenceCameraHandlerAccessible(presenceCameraThing, presenceCamera);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchSurveillanceOn() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(cameraStatusChannelUID, OnOffType.ON);
|
||||
|
||||
verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch on
|
||||
verify(requestExecutorMock).executeGETRequest(DUMMY_LOCAL_URL + "/command/changestatus?status=on");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchSurveillanceOff() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(cameraStatusChannelUID, OnOffType.OFF);
|
||||
|
||||
verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
|
||||
verify(requestExecutorMock).executeGETRequest(DUMMY_LOCAL_URL + "/command/changestatus?status=off");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchSurveillanceUnknownCommand() {
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(cameraStatusChannelUID, RefreshType.REFRESH);
|
||||
|
||||
verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
|
||||
// command
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchSurveillanceWithoutVPN() {
|
||||
handler.handleCommand(cameraStatusChannelUID, OnOffType.ON);
|
||||
|
||||
verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed when no VPN
|
||||
// address is set
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchFloodlightOn() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.ON);
|
||||
|
||||
verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch on
|
||||
verify(requestExecutorMock)
|
||||
.executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchFloodlightOff() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
|
||||
|
||||
verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
|
||||
verify(requestExecutorMock).executeGETRequest(
|
||||
DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchFloodlightOffWithAutoModeOn() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
|
||||
assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
|
||||
|
||||
verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
|
||||
verify(requestExecutorMock).executeGETRequest(
|
||||
DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22auto%22%7D");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchFloodlightOnAddressChanged() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.ON);
|
||||
// 1.) execute ping + 2.) execute switch on
|
||||
verify(requestExecutorMock, times(2)).executeGETRequest(any());
|
||||
verify(requestExecutorMock)
|
||||
.executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
|
||||
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
|
||||
// 1.) execute ping + 2.) execute switch on + 3.) execute switch off
|
||||
verify(requestExecutorMock, times(3)).executeGETRequest(any());
|
||||
verify(requestExecutorMock)
|
||||
.executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
|
||||
verify(requestExecutorMock).executeGETRequest(
|
||||
DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
|
||||
|
||||
final String newDummyVPNURL = DUMMY_VPN_URL + "2";
|
||||
final String newDummyLocalURL = DUMMY_LOCAL_URL + "2";
|
||||
final Optional<String> newDummyPingResponse = createPingResponseContent(newDummyLocalURL);
|
||||
|
||||
when(requestExecutorMock.executeGETRequest(newDummyVPNURL + "/command/ping")).thenReturn(newDummyPingResponse);
|
||||
|
||||
presenceCamera.setVpnUrl(newDummyVPNURL);
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.ON);
|
||||
// 1.) execute ping + 2.) execute switch on + 3.) execute switch off + 4.) execute ping + 5.) execute switch on
|
||||
verify(requestExecutorMock, times(5)).executeGETRequest(any());
|
||||
verify(requestExecutorMock)
|
||||
.executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
|
||||
verify(requestExecutorMock).executeGETRequest(
|
||||
DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
|
||||
verify(requestExecutorMock).executeGETRequest(
|
||||
newDummyLocalURL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchFloodlightUnknownCommand() {
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(floodlightChannelUID, RefreshType.REFRESH);
|
||||
|
||||
verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
|
||||
// command
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchFloodlightAutoModeOn() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
|
||||
handler.handleCommand(floodlightAutoModeChannelUID, OnOffType.ON);
|
||||
|
||||
verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch
|
||||
// auto-mode on
|
||||
verify(requestExecutorMock).executeGETRequest(
|
||||
DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22auto%22%7D");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchFloodlightAutoModeOff() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
|
||||
handler.handleCommand(floodlightAutoModeChannelUID, OnOffType.OFF);
|
||||
|
||||
verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
|
||||
verify(requestExecutorMock).executeGETRequest(
|
||||
DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandSwitchFloodlightAutoModeUnknownCommand() {
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(floodlightAutoModeChannelUID, RefreshType.REFRESH);
|
||||
|
||||
verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
|
||||
// command
|
||||
}
|
||||
|
||||
/**
|
||||
* The request "fails" because there is no response content of the ping command.
|
||||
*/
|
||||
@Test
|
||||
public void testHandleCommandRequestFailed() {
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.ON);
|
||||
|
||||
verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandWithoutVPN() {
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.ON);
|
||||
|
||||
verify(requestExecutorMock, never()).executeGETRequest(any()); // no executions because the VPN URL is still
|
||||
// unknown
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandPingFailedNULLResponse() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(Optional.of(""));
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.ON);
|
||||
|
||||
verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandPingFailedEmptyResponse() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(Optional.of(""));
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.ON);
|
||||
|
||||
verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandPingFailedWrongResponse() {
|
||||
when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping"))
|
||||
.thenReturn(Optional.of("{ \"message\": \"error\" }"));
|
||||
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
handler.handleCommand(floodlightChannelUID, OnOffType.ON);
|
||||
|
||||
verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleCommandModuleNULL() {
|
||||
NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
|
||||
timeZoneProviderMock);
|
||||
handlerWithoutModule.handleCommand(floodlightChannelUID, OnOffType.ON);
|
||||
|
||||
verify(requestExecutorMock, never()).executeGETRequest(any()); // no executions because the thing isn't
|
||||
// initialized
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyCommonChannel() {
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_CAMERA_STATUS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightOn() {
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
|
||||
assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightOff() {
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightAuto() {
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
|
||||
// When the floodlight is set to auto-mode it is currently off.
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightWithoutLightModeState() {
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightModuleNULL() {
|
||||
NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
|
||||
timeZoneProviderMock);
|
||||
assertEquals(UnDefType.UNDEF, handlerWithoutModule.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightAutoModeFloodlightAuto() {
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
|
||||
assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightAutoModeFloodlightOn() {
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
|
||||
// When the floodlight is initially on (on starting the binding), there is no information about if the auto-mode
|
||||
// was set before. Therefore the auto-mode is detected as deactivated / off.
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightAutoModeFloodlightOff() {
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
|
||||
// When the floodlight is initially off (on starting the binding), the auto-mode isn't set.
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightScenarioWithAutoMode() {
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
|
||||
assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
|
||||
// The auto-mode was initially set, after that the floodlight was switched on by the user.
|
||||
// In this case the binding should still know that the auto-mode is/was set.
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
|
||||
assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
|
||||
// After that the user switched off the floodlight.
|
||||
// In this case the binding should still know that the auto-mode is/was set.
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
|
||||
assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightScenarioWithoutAutoMode() {
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
|
||||
// The auto-mode wasn't set, after that the floodlight was switched on by the user.
|
||||
// In this case the binding should still know that the auto-mode isn't/wasn't set.
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
|
||||
// After that the user switched off the floodlight.
|
||||
// In this case the binding should still know that the auto-mode isn't/wasn't set.
|
||||
presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNAThingPropertyFloodlightAutoModeModuleNULL() {
|
||||
NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
|
||||
timeZoneProviderMock);
|
||||
assertEquals(UnDefType.UNDEF, handlerWithoutModule.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStreamURL() {
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
|
||||
assertTrue(streamURL.isPresent());
|
||||
assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index.m3u8", streamURL.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStreamURLLocal() {
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
presenceCamera.setIsLocal(true);
|
||||
|
||||
Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
|
||||
assertTrue(streamURL.isPresent());
|
||||
assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index_local.m3u8", streamURL.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStreamURLNotLocal() {
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
presenceCamera.setIsLocal(false);
|
||||
|
||||
Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
|
||||
assertTrue(streamURL.isPresent());
|
||||
assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index.m3u8", streamURL.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStreamURLWithoutVPN() {
|
||||
Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
|
||||
assertFalse(streamURL.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLivePictureURLState() {
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
|
||||
State livePictureURLState = handler.getLivePictureURLState();
|
||||
assertEquals(new StringType(DUMMY_VPN_URL + "/live/snapshot_720.jpg"), livePictureURLState);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLivePictureURLStateWithoutVPN() {
|
||||
State livePictureURLState = handler.getLivePictureURLState();
|
||||
assertEquals(UnDefType.UNDEF, livePictureURLState);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLiveStreamState() {
|
||||
presenceCamera.setVpnUrl(DUMMY_VPN_URL);
|
||||
|
||||
State liveStreamState = handler.getLiveStreamState();
|
||||
assertEquals(new StringType(DUMMY_VPN_URL + "/live/index.m3u8"), liveStreamState);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLiveStreamStateWithoutVPN() {
|
||||
State liveStreamState = handler.getLiveStreamState();
|
||||
assertEquals(UnDefType.UNDEF, liveStreamState);
|
||||
}
|
||||
|
||||
private static Optional<String> createPingResponseContent(final String localURL) {
|
||||
return Optional.of("{\"local_url\":\"" + localURL + "\",\"product_name\":\"Welcome Netatmo\"}");
|
||||
}
|
||||
|
||||
private interface RequestExecutor {
|
||||
|
||||
Optional<String> executeGETRequest(String url);
|
||||
}
|
||||
|
||||
private class NAPresenceCameraHandlerAccessible extends NAPresenceCameraHandler {
|
||||
|
||||
private NAPresenceCameraHandlerAccessible(Thing thing, NAWelcomeCamera presenceCamera) {
|
||||
super(thing, timeZoneProviderMock);
|
||||
setModule(presenceCamera);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull Optional<@NonNull String> executeGETRequest(@NonNull String url) {
|
||||
return requestExecutorMock.executeGETRequest(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull State getLivePictureURLState() {
|
||||
return super.getLivePictureURLState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull State getLiveStreamState() {
|
||||
return super.getLiveStreamState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.netatmo.internal.welcome;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.internal.ThingImpl;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.openhab.binding.netatmo.internal.NetatmoBindingConstants;
|
||||
import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
|
||||
import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
|
||||
|
||||
import io.swagger.client.model.NAWelcomeEvent;
|
||||
import io.swagger.client.model.NAWelcomeHome;
|
||||
import io.swagger.client.model.NAWelcomeHomeData;
|
||||
import io.swagger.client.model.NAWelcomeSubEvent;
|
||||
|
||||
/**
|
||||
* @author Sven Strohschein - Initial contribution
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class NAWelcomeHomeHandlerTest {
|
||||
|
||||
private static final String DUMMY_HOME_ID = "1";
|
||||
|
||||
@Mock
|
||||
private TimeZoneProvider timeZoneProviderMock;
|
||||
private NAWelcomeHomeHandlerAccessible handler;
|
||||
@Mock
|
||||
private NetatmoBridgeHandler bridgeHandlerMock;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
Thing welcomeHomeThing = new ThingImpl(new ThingTypeUID("netatmo", "NAWelcomeHome"), "1");
|
||||
handler = new NAWelcomeHomeHandlerAccessible(welcomeHomeThing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateReadingsWithEvents() {
|
||||
NAWelcomeEvent event1 = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.PERSON);
|
||||
NAWelcomeEvent event2 = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
|
||||
|
||||
NAWelcomeHome home = new NAWelcomeHome();
|
||||
home.setId(DUMMY_HOME_ID);
|
||||
home.setEvents(Arrays.asList(event1, event2));
|
||||
|
||||
NAWelcomeHomeData homeData = new NAWelcomeHomeData();
|
||||
homeData.setHomes(Collections.singletonList(home));
|
||||
|
||||
when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
|
||||
|
||||
handler.updateReadings();
|
||||
|
||||
// the second (last) event is expected
|
||||
assertEquals(new StringType("movement"),
|
||||
handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
|
||||
home.setEvents(Arrays.asList(event2, event1));
|
||||
// the second (last) event is still expected (independent from the order of these are added)
|
||||
assertEquals(new StringType("movement"),
|
||||
handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateReadingsWith1Event() {
|
||||
NAWelcomeEvent event = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.PERSON);
|
||||
|
||||
NAWelcomeHome home = new NAWelcomeHome();
|
||||
home.setId(DUMMY_HOME_ID);
|
||||
home.setEvents(Collections.singletonList(event));
|
||||
|
||||
NAWelcomeHomeData homeData = new NAWelcomeHomeData();
|
||||
homeData.setHomes(Collections.singletonList(home));
|
||||
|
||||
when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
|
||||
|
||||
handler.updateReadings();
|
||||
|
||||
assertEquals(new StringType("person"),
|
||||
handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateReadingsNoEvents() {
|
||||
NAWelcomeHome home = new NAWelcomeHome();
|
||||
home.setId(DUMMY_HOME_ID);
|
||||
|
||||
NAWelcomeHomeData homeData = new NAWelcomeHomeData();
|
||||
homeData.setHomes(Collections.singletonList(home));
|
||||
|
||||
when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
|
||||
|
||||
handler.updateReadings();
|
||||
|
||||
assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateReadingsEmptyHomeData() {
|
||||
NAWelcomeHomeData homeData = new NAWelcomeHomeData();
|
||||
|
||||
when(bridgeHandlerMock.getWelcomeDataBody(any())).thenReturn(Optional.of(homeData));
|
||||
|
||||
handler.updateReadings();
|
||||
|
||||
assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateReadingsNoHomeData() {
|
||||
handler.updateReadings();
|
||||
|
||||
assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTriggerChannelIfRequired() {
|
||||
NAWelcomeEvent event1 = createPresenceEvent(1592661881, NAWelcomeSubEvent.TypeEnum.ANIMAL);
|
||||
NAWelcomeEvent event2 = createPresenceEvent(1592661882, NAWelcomeSubEvent.TypeEnum.HUMAN);
|
||||
NAWelcomeEvent event3 = createEvent(1592661883, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
|
||||
|
||||
NAWelcomeHome home = new NAWelcomeHome();
|
||||
home.setId(DUMMY_HOME_ID);
|
||||
home.setEvents(Collections.singletonList(event1));
|
||||
|
||||
NAWelcomeHomeData homeData = new NAWelcomeHomeData();
|
||||
homeData.setHomes(Collections.singletonList(home));
|
||||
|
||||
when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
// No triggered event is expected, because the binding is just started (with existing events).
|
||||
assertEquals(0, handler.getTriggerChannelCount());
|
||||
|
||||
home.setEvents(Arrays.asList(event1, event2));
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
// 1 triggered event is expected, because there is 1 new event since binding start (outdoor / detected human).
|
||||
assertEquals(1, handler.getTriggerChannelCount());
|
||||
assertEquals(new StringType("outdoor"),
|
||||
handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
assertEquals("HUMAN", handler.getLastDetectedObject());
|
||||
|
||||
home.setEvents(Arrays.asList(event1, event2));
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
// No new triggered event is expected, because there are still the same events as before the refresh.
|
||||
assertEquals(1, handler.getTriggerChannelCount());
|
||||
assertEquals(new StringType("outdoor"),
|
||||
handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
assertEquals("HUMAN", handler.getLastDetectedObject());
|
||||
|
||||
home.setEvents(Arrays.asList(event1, event2, event3));
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
// 1 new triggered event is expected (2 in sum), because there is 1 new event since the last triggered event
|
||||
// (movement after outdoor / detected human).
|
||||
assertEquals(2, handler.getTriggerChannelCount());
|
||||
assertEquals(new StringType("movement"),
|
||||
handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
assertEquals("MOVEMENT", handler.getLastDetectedObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTriggerChannelIfRequiredNoEventAvailable() {
|
||||
NAWelcomeHome home = new NAWelcomeHome();
|
||||
home.setId(DUMMY_HOME_ID);
|
||||
|
||||
NAWelcomeHomeData homeData = new NAWelcomeHomeData();
|
||||
homeData.setHomes(Collections.singletonList(home));
|
||||
|
||||
when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
// No triggered event is expected, because there aren't any events (the collection is NULL)
|
||||
assertEquals(0, handler.getTriggerChannelCount());
|
||||
|
||||
home.setEvents(Collections.emptyList());
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
// No triggered event is expected, because there aren't any events (the collection is empty)
|
||||
assertEquals(0, handler.getTriggerChannelCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTriggerChannelIfRequiredPersonMovement() {
|
||||
NAWelcomeHome home = initHome();
|
||||
|
||||
NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
|
||||
event.setPersonId("1");
|
||||
|
||||
home.getEvents().add(event);
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
assertEquals(1, handler.getTriggerChannelCount());
|
||||
assertEquals(new StringType("movement"),
|
||||
handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
assertEquals("HUMAN", handler.getLastDetectedObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTriggerChannelIfRequiredHumanMovement() {
|
||||
NAWelcomeHome home = initHome();
|
||||
|
||||
NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
|
||||
event.setCategory(NAWelcomeEvent.CategoryEnum.HUMAN);
|
||||
|
||||
home.getEvents().add(event);
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
assertEquals(1, handler.getTriggerChannelCount());
|
||||
assertEquals(new StringType("movement"),
|
||||
handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
assertEquals("HUMAN", handler.getLastDetectedObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTriggerChannelIfRequiredAnimalMovement() {
|
||||
NAWelcomeHome home = initHome();
|
||||
|
||||
NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
|
||||
event.setCategory(NAWelcomeEvent.CategoryEnum.ANIMAL);
|
||||
|
||||
home.getEvents().add(event);
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
assertEquals(1, handler.getTriggerChannelCount());
|
||||
assertEquals(new StringType("movement"),
|
||||
handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
assertEquals("ANIMAL", handler.getLastDetectedObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTriggerChannelIfRequiredVehicleMovement() {
|
||||
NAWelcomeHome home = initHome();
|
||||
|
||||
NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
|
||||
event.setCategory(NAWelcomeEvent.CategoryEnum.VEHICLE);
|
||||
|
||||
home.getEvents().add(event);
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
assertEquals(1, handler.getTriggerChannelCount());
|
||||
assertEquals(new StringType("movement"),
|
||||
handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
|
||||
assertEquals("VEHICLE", handler.getLastDetectedObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchDetectedObjectEnums() {
|
||||
assertArrayEquals(
|
||||
"The detected object enums aren't equal anymore, that could lead to a bug! Please check the usages!",
|
||||
Arrays.stream(NAWelcomeEvent.CategoryEnum.values()).map(Enum::name).toArray(),
|
||||
Arrays.stream(NAWelcomeSubEvent.TypeEnum.values()).map(Enum::name).toArray());
|
||||
}
|
||||
|
||||
private NAWelcomeHome initHome() {
|
||||
NAWelcomeEvent initLastEvent = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
|
||||
|
||||
NAWelcomeHome home = new NAWelcomeHome();
|
||||
home.setId(DUMMY_HOME_ID);
|
||||
|
||||
List<NAWelcomeEvent> events = new ArrayList<>();
|
||||
events.add(initLastEvent);
|
||||
home.setEvents(events);
|
||||
|
||||
NAWelcomeHomeData homeData = new NAWelcomeHomeData();
|
||||
homeData.setHomes(Collections.singletonList(home));
|
||||
|
||||
when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
|
||||
|
||||
triggerCameraEvents();
|
||||
|
||||
return home;
|
||||
}
|
||||
|
||||
private void triggerCameraEvents() {
|
||||
handler.updateReadings();
|
||||
handler.triggerChannelIfRequired(NetatmoBindingConstants.CHANNEL_CAMERA_EVENT);
|
||||
}
|
||||
|
||||
private static NAWelcomeEvent createPresenceEvent(int eventTime, NAWelcomeSubEvent.TypeEnum detectedObjectType) {
|
||||
NAWelcomeSubEvent subEvent = new NAWelcomeSubEvent();
|
||||
subEvent.setTime(eventTime);
|
||||
subEvent.setType(detectedObjectType);
|
||||
|
||||
NAWelcomeEvent event = createEvent(eventTime, NAWebhookCameraEvent.EventTypeEnum.OUTDOOR);
|
||||
event.setEventList(Collections.singletonList(subEvent));
|
||||
return event;
|
||||
}
|
||||
|
||||
private static NAWelcomeEvent createEvent(int eventTime, NAWebhookCameraEvent.EventTypeEnum eventType) {
|
||||
NAWelcomeEvent event = new NAWelcomeEvent();
|
||||
event.setType(eventType.toString());
|
||||
event.setTime(eventTime);
|
||||
return event;
|
||||
}
|
||||
|
||||
private class NAWelcomeHomeHandlerAccessible extends NAWelcomeHomeHandler {
|
||||
|
||||
private int triggerChannelCount;
|
||||
private String lastDetectedObject;
|
||||
|
||||
private NAWelcomeHomeHandlerAccessible(Thing thing) {
|
||||
super(thing, timeZoneProviderMock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<NetatmoBridgeHandler> getBridgeHandler() {
|
||||
return Optional.of(bridgeHandlerMock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getId() {
|
||||
return DUMMY_HOME_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void triggerChannel(@NonNull String channelID, @NonNull String event) {
|
||||
triggerChannelCount++;
|
||||
lastDetectedObject = event;
|
||||
super.triggerChannel(channelID, event);
|
||||
}
|
||||
|
||||
private int getTriggerChannelCount() {
|
||||
return triggerChannelCount;
|
||||
}
|
||||
|
||||
public String getLastDetectedObject() {
|
||||
return lastDetectedObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user