[netatmo] Fix erroneous local URL handling (#16492)

* Wrong local URL handling
* Adds configuration element ipAddress to Cameras (Welcome, Doorbell, Presence) so they remain reachable if API answer is in an incorrect network
* Removing CameraConfiguration so binding does not break if thing configuration is not up-to-date

---------

Signed-off-by: clinique <gael@lhopital.org>
Signed-off-by: gael@lhopital.org <gael@lhopital.org>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Gaël L'hopital 2024-03-15 11:24:27 +01:00 committed by Ciprian Pascu
parent f3bf285b85
commit 55c84385ab
14 changed files with 136 additions and 93 deletions

View File

@ -77,18 +77,18 @@ Once authentication process has been done, current refreshToken is stored in `/O
| Thing Type | Type | Netatmo Object | Description | Thing Parameters | | Thing Type | Type | Netatmo Object | Description | Thing Parameters |
| --------------- | ------ | -------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | --------------- | ------ | -------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| account | Bridge | N/A | This bridge represents an account, gateway to Netatmo API. | clientId, clientSecret, username, password, webHookUrl, reconnectInterval | | account | Bridge | N/A | This bridge represents an account, gateway to Netatmo API. | clientId, clientSecret, username, password, webHookUrl, reconnectInterval |
| home | Bridge | NAHome | A home hosting Security or Energy devices and modules. | id, refreshInterval | | home | Bridge | NAHome | A home hosting Security or Energy devices and modules. | id, refreshInterval, energyId, securityId |
| person | Thing | NAPerson | A person known by your Netatmo system. | id | | person | Thing | NAPerson | A person known by your Netatmo system. | id |
| welcome | Thing | NACamera | The Netatmo Smart Indoor Camera (Welcome). | id | | welcome | Thing | NACamera | The Netatmo Smart Indoor Camera (Welcome). | id, ipAddress |
| presence | Thing | NOC | The Netatmo Smart Outdoor Camera (Presence) camera with or without siren. | id | | presence | Thing | NOC | The Netatmo Smart Outdoor Camera (Presence) camera with or without siren. | id, ipAddress |
| siren | Thing | NIS | The Netatmo Smart Indoor Siren. | id | | siren | Thing | NIS | The Netatmo Smart Indoor Siren. | id |
| doorbell | Thing | NDB | The Netatmo Smart Video Doorbell device. | id | | doorbell | Thing | NDB | The Netatmo Smart Video Doorbell device. | id, ipAddress |
| weather-station | Bridge | NAMain | Main indoor module reporting temperature, humidity, pressure, air quality and sound level. | id | | weather-station | Bridge | NAMain | Main indoor module reporting temperature, humidity, pressure, air quality and sound level. | id, refreshInterval |
| outdoor | Thing | NAModule1 | Outdoor module reporting temperature and humidity. | id | | outdoor | Thing | NAModule1 | Outdoor module reporting temperature and humidity. | id |
| wind | Thing | NAModule2 | Wind sensor reporting wind angle and strength. | id | | wind | Thing | NAModule2 | Wind sensor reporting wind angle and strength. | id |
| rain | Thing | NAModule3 | Rain Gauge measuring precipitation. | id | | rain | Thing | NAModule3 | Rain Gauge measuring precipitation. | id |
| indoor | Thing | NAModule4 | Additional indoor module reporting temperature, humidity and CO2 level. | id | | indoor | Thing | NAModule4 | Additional indoor module reporting temperature, humidity and CO2 level. | id |
| home-coach | Thing | NHC | Healthy home coach reporting health-index, temperature, humidity, pressure, air quality, sound level. | id | | home-coach | Thing | NHC | Healthy home coach reporting health-index, temperature, humidity, pressure, air quality, sound level. | id, refreshInterval |
| plug | Thing | NAPlug | The relay connected to the boiler controlling a Thermostat and zero or more valves. | id | | plug | Thing | NAPlug | The relay connected to the boiler controlling a Thermostat and zero or more valves. | id |
| thermostat | Thing | NATherm1 | The Thermostat device placed in a given room. | id | | thermostat | Thing | NATherm1 | The Thermostat device placed in a given room. | id |
| room | Thing | NARoom | A room in your house. | id | | room | Thing | NARoom | A room in your house. | id |

View File

@ -64,67 +64,70 @@ import org.openhab.core.thing.ThingTypeUID;
*/ */
@NonNullByDefault @NonNullByDefault
public enum ModuleType { public enum ModuleType {
UNKNOWN(FeatureArea.NONE, "", 1, null, Set.of()), UNKNOWN(FeatureArea.NONE, "", 1, "virtual", null, Set.of()),
ACCOUNT(FeatureArea.NONE, "", 1, null, Set.of(), new ChannelGroup(ApiBridgeChannelHelper.class, GROUP_MONITORING)), ACCOUNT(FeatureArea.NONE, "", 1, "api_bridge", null, Set.of(),
new ChannelGroup(ApiBridgeChannelHelper.class, GROUP_MONITORING)),
HOME(FeatureArea.NONE, "NAHome", 1, ACCOUNT, HOME(FeatureArea.NONE, "NAHome", 1, "home", ACCOUNT,
Set.of(DeviceCapability.class, HomeCapability.class, ChannelHelperCapability.class), Set.of(DeviceCapability.class, HomeCapability.class, ChannelHelperCapability.class),
new ChannelGroup(SecurityChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_SECURITY), new ChannelGroup(SecurityChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_SECURITY),
new ChannelGroup(EnergyChannelHelper.class, GROUP_ENERGY)), new ChannelGroup(EnergyChannelHelper.class, GROUP_ENERGY)),
PERSON(FeatureArea.SECURITY, "NAPerson", 1, HOME, Set.of(PersonCapability.class, ChannelHelperCapability.class), PERSON(FeatureArea.SECURITY, "NAPerson", 1, "virtual", HOME,
Set.of(PersonCapability.class, ChannelHelperCapability.class),
new ChannelGroup(PersonChannelHelper.class, GROUP_PERSON), new ChannelGroup(PersonChannelHelper.class, GROUP_PERSON),
new ChannelGroup(EventPersonChannelHelper.class, GROUP_PERSON_LAST_EVENT)), new ChannelGroup(EventPersonChannelHelper.class, GROUP_PERSON_LAST_EVENT)),
WELCOME(FeatureArea.SECURITY, "NACamera", 1, HOME, Set.of(CameraCapability.class, ChannelHelperCapability.class), WELCOME(FeatureArea.SECURITY, "NACamera", 1, "camera", HOME,
ChannelGroup.SIGNAL, ChannelGroup.EVENT, Set.of(CameraCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, ChannelGroup.EVENT,
new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE)), new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE)),
TAG(FeatureArea.SECURITY, "NACamDoorTag", 1, WELCOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL, TAG(FeatureArea.SECURITY, "NACamDoorTag", 1, "device", WELCOME, Set.of(ChannelHelperCapability.class),
ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(DoorTagChannelHelper.class, GROUP_TAG)), ChannelGroup.SIGNAL, ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP,
new ChannelGroup(DoorTagChannelHelper.class, GROUP_TAG)),
SIREN(FeatureArea.SECURITY, "NIS", 1, WELCOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL, SIREN(FeatureArea.SECURITY, "NIS", 1, "device", WELCOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(SirenChannelHelper.class, GROUP_SIREN)), ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(SirenChannelHelper.class, GROUP_SIREN)),
PRESENCE(FeatureArea.SECURITY, "NOC", 1, HOME, Set.of(PresenceCapability.class, ChannelHelperCapability.class), PRESENCE(FeatureArea.SECURITY, "NOC", 1, "camera", HOME,
ChannelGroup.SIGNAL, ChannelGroup.EVENT, Set.of(PresenceCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, ChannelGroup.EVENT,
new ChannelGroup(PresenceChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE, new ChannelGroup(PresenceChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE,
GROUP_PRESENCE), GROUP_PRESENCE),
new ChannelGroup(EventCameraChannelHelper.class, GROUP_SUB_EVENT)), new ChannelGroup(EventCameraChannelHelper.class, GROUP_SUB_EVENT)),
DOORBELL(FeatureArea.SECURITY, "NDB", 1, HOME, Set.of(DoorbellCapability.class, ChannelHelperCapability.class), DOORBELL(FeatureArea.SECURITY, "NDB", 1, "camera", HOME,
ChannelGroup.SIGNAL, Set.of(DoorbellCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_DOORBELL_STATUS, new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_DOORBELL_STATUS,
GROUP_DOORBELL_LIVE), GROUP_DOORBELL_LIVE),
new ChannelGroup(EventCameraChannelHelper.class, GROUP_DOORBELL_LAST_EVENT, GROUP_DOORBELL_SUB_EVENT)), new ChannelGroup(EventCameraChannelHelper.class, GROUP_DOORBELL_LAST_EVENT, GROUP_DOORBELL_SUB_EVENT)),
WEATHER_STATION(FeatureArea.WEATHER, "NAMain", 1, ACCOUNT, WEATHER_STATION(FeatureArea.WEATHER, "NAMain", 1, "configurable", ACCOUNT,
Set.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class, Set.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class,
ChannelHelperCapability.class), ChannelHelperCapability.class),
ChannelGroup.SIGNAL, ChannelGroup.HUMIDITY, ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.SIGNAL, ChannelGroup.HUMIDITY, ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE,
ChannelGroup.AIR_QUALITY, ChannelGroup.LOCATION, ChannelGroup.NOISE, ChannelGroup.TEMP_INSIDE_EXT, ChannelGroup.AIR_QUALITY, ChannelGroup.LOCATION, ChannelGroup.NOISE, ChannelGroup.TEMP_INSIDE_EXT,
new ChannelGroup(PressureChannelHelper.class, MeasureClass.PRESSURE, GROUP_TYPE_PRESSURE_EXTENDED)), new ChannelGroup(PressureChannelHelper.class, MeasureClass.PRESSURE, GROUP_TYPE_PRESSURE_EXTENDED)),
OUTDOOR(FeatureArea.WEATHER, "NAModule1", 1, WEATHER_STATION, OUTDOOR(FeatureArea.WEATHER, "NAModule1", 1, "device", WEATHER_STATION,
Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, ChannelGroup.HUMIDITY, Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, ChannelGroup.HUMIDITY,
ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.TEMP_OUTSIDE_EXT), ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.TEMP_OUTSIDE_EXT),
WIND(FeatureArea.WEATHER, "NAModule2", 1, WEATHER_STATION, Set.of(ChannelHelperCapability.class), WIND(FeatureArea.WEATHER, "NAModule2", 1, "device", WEATHER_STATION, Set.of(ChannelHelperCapability.class),
ChannelGroup.SIGNAL, ChannelGroup.TSTAMP_EXT, ChannelGroup.BATTERY, ChannelGroup.SIGNAL, ChannelGroup.TSTAMP_EXT, ChannelGroup.BATTERY,
new ChannelGroup(WindChannelHelper.class, GROUP_WIND)), new ChannelGroup(WindChannelHelper.class, GROUP_WIND)),
RAIN(FeatureArea.WEATHER, "NAModule3", 1, WEATHER_STATION, RAIN(FeatureArea.WEATHER, "NAModule3", 1, "device", WEATHER_STATION,
Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY,
new ChannelGroup(RainChannelHelper.class, MeasureClass.RAIN_QUANTITY, GROUP_RAIN)), new ChannelGroup(RainChannelHelper.class, MeasureClass.RAIN_QUANTITY, GROUP_RAIN)),
INDOOR(FeatureArea.WEATHER, "NAModule4", 1, WEATHER_STATION, INDOOR(FeatureArea.WEATHER, "NAModule4", 1, "device", WEATHER_STATION,
Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.HUMIDITY, ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.HUMIDITY,
ChannelGroup.TEMP_INSIDE_EXT, ChannelGroup.AIR_QUALITY), ChannelGroup.TEMP_INSIDE_EXT, ChannelGroup.AIR_QUALITY),
HOME_COACH(FeatureArea.AIR_CARE, "NHC", 1, ACCOUNT, HOME_COACH(FeatureArea.AIR_CARE, "NHC", 1, "configurable", ACCOUNT,
Set.of(DeviceCapability.class, AirCareCapability.class, MeasureCapability.class, Set.of(DeviceCapability.class, AirCareCapability.class, MeasureCapability.class,
ChannelHelperCapability.class), ChannelHelperCapability.class),
ChannelGroup.LOCATION, ChannelGroup.SIGNAL, ChannelGroup.NOISE, ChannelGroup.HUMIDITY, ChannelGroup.LOCATION, ChannelGroup.SIGNAL, ChannelGroup.NOISE, ChannelGroup.HUMIDITY,
@ -132,24 +135,26 @@ public enum ModuleType {
new ChannelGroup(AirQualityChannelHelper.class, GROUP_TYPE_AIR_QUALITY_EXTENDED), new ChannelGroup(AirQualityChannelHelper.class, GROUP_TYPE_AIR_QUALITY_EXTENDED),
new ChannelGroup(PressureChannelHelper.class, MeasureClass.PRESSURE, GROUP_PRESSURE)), new ChannelGroup(PressureChannelHelper.class, MeasureClass.PRESSURE, GROUP_PRESSURE)),
PLUG(FeatureArea.ENERGY, "NAPlug", 1, HOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL), PLUG(FeatureArea.ENERGY, "NAPlug", 1, "device", HOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL),
VALVE(FeatureArea.ENERGY, "NRV", 1, PLUG, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL, VALVE(FeatureArea.ENERGY, "NRV", 1, "device", PLUG, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.BATTERY_EXT), ChannelGroup.BATTERY_EXT),
THERMOSTAT(FeatureArea.ENERGY, "NATherm1", 1, PLUG, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL, THERMOSTAT(FeatureArea.ENERGY, "NATherm1", 1, "device", PLUG, Set.of(ChannelHelperCapability.class),
ChannelGroup.BATTERY_EXT, new ChannelGroup(Therm1ChannelHelper.class, GROUP_TYPE_TH_PROPERTIES)), ChannelGroup.SIGNAL, ChannelGroup.BATTERY_EXT,
new ChannelGroup(Therm1ChannelHelper.class, GROUP_TYPE_TH_PROPERTIES)),
ROOM(FeatureArea.ENERGY, "NARoom", 1, HOME, Set.of(RoomCapability.class, ChannelHelperCapability.class), ROOM(FeatureArea.ENERGY, "NARoom", 1, "virtual", HOME, Set.of(RoomCapability.class, ChannelHelperCapability.class),
new ChannelGroup(RoomChannelHelper.class, GROUP_TYPE_ROOM_PROPERTIES, GROUP_TYPE_ROOM_TEMPERATURE), new ChannelGroup(RoomChannelHelper.class, GROUP_TYPE_ROOM_PROPERTIES, GROUP_TYPE_ROOM_TEMPERATURE),
new ChannelGroup(SetpointChannelHelper.class, GROUP_SETPOINT)), new ChannelGroup(SetpointChannelHelper.class, GROUP_SETPOINT)),
SMOKE_DETECTOR(FeatureArea.SECURITY, "NSD", 1, HOME, SMOKE_DETECTOR(FeatureArea.SECURITY, "NSD", 1, "device", HOME,
Set.of(AlarmEventCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, Set.of(AlarmEventCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT), ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT),
CO_DETECTOR(FeatureArea.SECURITY, "NCO", 1, HOME, Set.of(AlarmEventCapability.class, ChannelHelperCapability.class), CO_DETECTOR(FeatureArea.SECURITY, "NCO", 1, "device", HOME,
ChannelGroup.SIGNAL, ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT); Set.of(AlarmEventCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT);
public static final EnumSet<ModuleType> AS_SET = EnumSet.allOf(ModuleType.class); public static final EnumSet<ModuleType> AS_SET = EnumSet.allOf(ModuleType.class);
@ -160,8 +165,9 @@ public enum ModuleType {
public final FeatureArea feature; public final FeatureArea feature;
public final String apiName; public final String apiName;
public final String thingTypeVersion; public final String thingTypeVersion;
public final URI configDescription;
ModuleType(FeatureArea feature, String apiName, int thingTypeVersion, @Nullable ModuleType bridge, ModuleType(FeatureArea feature, String apiName, int thingTypeVersion, String config, @Nullable ModuleType bridge,
Set<Class<? extends Capability>> capabilities, ChannelGroup... channelGroups) { Set<Class<? extends Capability>> capabilities, ChannelGroup... channelGroups) {
this.bridgeType = Optional.ofNullable(bridge); this.bridgeType = Optional.ofNullable(bridge);
this.feature = feature; this.feature = feature;
@ -170,6 +176,7 @@ public enum ModuleType {
this.channelGroups = Set.of(channelGroups); this.channelGroups = Set.of(channelGroups);
this.thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase().replace("_", "-")); this.thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase().replace("_", "-"));
this.thingTypeVersion = Integer.toString(thingTypeVersion); this.thingTypeVersion = Integer.toString(thingTypeVersion);
this.configDescription = URI.create(BINDING_ID + ":" + config);
} }
public boolean isLogical() { public boolean isLogical() {
@ -203,13 +210,6 @@ public enum ModuleType {
return bridgeType.orElse(UNKNOWN); return bridgeType.orElse(UNKNOWN);
} }
public URI getConfigDescription() {
return URI.create(BINDING_ID + ":"
+ (equals(ACCOUNT) ? "api_bridge"
: equals(HOME) ? "home"
: (isLogical() ? "virtual" : UNKNOWN.equals(getBridge()) ? "configurable" : "device")));
}
public int getDepth() { public int getDepth() {
ModuleType parent = getBridge(); ModuleType parent = getBridge();
return parent == UNKNOWN ? 1 : parent.getDepth() + 1; return parent == UNKNOWN ? 1 : parent.getDepth() + 1;

View File

@ -32,7 +32,6 @@ import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
import org.openhab.binding.netatmo.internal.handler.capability.ParentUpdateCapability; import org.openhab.binding.netatmo.internal.handler.capability.ParentUpdateCapability;
import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability; import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability;
import org.openhab.binding.netatmo.internal.handler.capability.RestCapability; import org.openhab.binding.netatmo.internal.handler.capability.RestCapability;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel; import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
@ -69,6 +68,10 @@ public interface CommonInterface {
void updateState(ChannelUID channelUID, State state); void updateState(ChannelUID channelUID, State state);
default void updateState(String groupId, String id, State state) {
updateState(new ChannelUID(getThing().getUID(), groupId, id), state);
}
void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail, void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail,
@Nullable String thingStatusReason); @Nullable String thingStatusReason);
@ -108,11 +111,11 @@ public interface CommonInterface {
} }
default String getId() { default String getId() {
return getConfiguration().as(NAThingConfiguration.class).getId(); return getThingConfigAs(NAThingConfiguration.class).getId();
} }
default Configuration getConfiguration() { default <T> T getThingConfigAs(Class<T> configurationClass) {
return getThing().getConfiguration(); return getThing().getConfiguration().as(configurationClass);
} }
default Stream<Channel> getActiveChannels() { default Stream<Channel> getActiveChannels() {

View File

@ -23,8 +23,7 @@ import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent;
import org.openhab.binding.netatmo.internal.handler.CommonInterface; import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
import org.openhab.core.thing.ChannelUID; import org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
/** /**
@ -45,15 +44,12 @@ public class AlarmEventCapability extends HomeSecurityThingCapability {
protected void updateWebhookEvent(WebhookEvent event) { protected void updateWebhookEvent(WebhookEvent event) {
super.updateWebhookEvent(event); super.updateWebhookEvent(event);
final ThingUID thingUid = handler.getThing().getUID(); handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_TYPE, toStringType(event.getEventType()));
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_TYPE), handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_TIME, toDateTimeType(event.getTime()));
toStringType(event.getEventType())); handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE,
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_TIME), event.getSubTypeDescription().map(ChannelTypeUtils::toStringType).orElse(UnDefType.NULL));
toDateTimeType(event.getTime()));
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE),
event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL));
final String message = event.getName(); final String message = event.getName();
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_MESSAGE), handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_MESSAGE,
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message)); message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
} }

View File

@ -15,9 +15,13 @@ package org.openhab.binding.netatmo.internal.handler.capability;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilderException;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.AlimentationStatus; import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.AlimentationStatus;
@ -32,14 +36,15 @@ import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.StateOption; import org.openhab.core.types.StateOption;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* {@link CameraCapability} give to handle Welcome Camera specifics * {@link CameraCapability} give to handle Welcome Camera specifics
@ -49,6 +54,10 @@ import org.openhab.core.types.UnDefType;
*/ */
@NonNullByDefault @NonNullByDefault
public class CameraCapability extends HomeSecurityThingCapability { public class CameraCapability extends HomeSecurityThingCapability {
private static final String IP_ADDRESS = "ipAddress";
private final Logger logger = LoggerFactory.getLogger(CameraCapability.class);
private final CameraChannelHelper cameraHelper; private final CameraChannelHelper cameraHelper;
private final ChannelUID personChannelUID; private final ChannelUID personChannelUID;
@ -60,7 +69,7 @@ public class CameraCapability extends HomeSecurityThingCapability {
public CameraCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider, public CameraCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
List<ChannelHelper> channelHelpers) { List<ChannelHelper> channelHelpers) {
super(handler, descriptionProvider, channelHelpers); super(handler, descriptionProvider, channelHelpers);
this.personChannelUID = new ChannelUID(thing.getUID(), GROUP_LAST_EVENT, CHANNEL_EVENT_PERSON_ID); this.personChannelUID = new ChannelUID(thingUID, GROUP_LAST_EVENT, CHANNEL_EVENT_PERSON_ID);
this.cameraHelper = (CameraChannelHelper) channelHelpers.stream().filter(c -> c instanceof CameraChannelHelper) this.cameraHelper = (CameraChannelHelper) channelHelpers.stream().filter(c -> c instanceof CameraChannelHelper)
.findFirst().orElseThrow(() -> new IllegalArgumentException( .findFirst().orElseThrow(() -> new IllegalArgumentException(
"CameraCapability must find a CameraChannelHelper, please file a bug report.")); "CameraCapability must find a CameraChannelHelper, please file a bug report."));
@ -68,7 +77,6 @@ public class CameraCapability extends HomeSecurityThingCapability {
@Override @Override
public void initialize() { public void initialize() {
Thing thing = handler.getThing();
hasSubEventGroup = !thing.getChannelsOfGroup(GROUP_SUB_EVENT).isEmpty(); hasSubEventGroup = !thing.getChannelsOfGroup(GROUP_SUB_EVENT).isEmpty();
hasLastEventGroup = !thing.getChannelsOfGroup(GROUP_LAST_EVENT).isEmpty(); hasLastEventGroup = !thing.getChannelsOfGroup(GROUP_LAST_EVENT).isEmpty();
} }
@ -80,14 +88,15 @@ public class CameraCapability extends HomeSecurityThingCapability {
String newVpnUrl = newData.getVpnUrl(); String newVpnUrl = newData.getVpnUrl();
if (newVpnUrl != null && !newVpnUrl.equals(vpnUrl)) { if (newVpnUrl != null && !newVpnUrl.equals(vpnUrl)) {
// This will also decrease the number of requests emitted toward Netatmo API. // This will also decrease the number of requests emitted toward Netatmo API.
localUrl = newData.isLocal() ? getSecurityCapability().map(cap -> cap.ping(newVpnUrl)).orElse(null) : null; localUrl = newData.isLocal() ? ping(newVpnUrl) : null;
logger.debug("localUrl set to {} for camera {}", localUrl, thingUID);
cameraHelper.setUrls(newVpnUrl, localUrl); cameraHelper.setUrls(newVpnUrl, localUrl);
eventHelper.setUrls(newVpnUrl, localUrl); eventHelper.setUrls(newVpnUrl, localUrl);
} }
vpnUrl = newVpnUrl; vpnUrl = newVpnUrl;
if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus()) if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus())
|| !AlimentationStatus.ALIM_CORRECT_POWER.equals(newData.getAlimStatus())) { || !AlimentationStatus.ALIM_CORRECT_POWER.equals(newData.getAlimStatus())) {
statusReason = String.format("%s, %s", newData.getSdStatus(), newData.getAlimStatus()); statusReason = "%s, %s".formatted(newData.getSdStatus(), newData.getAlimStatus());
} }
} }
@ -96,11 +105,11 @@ public class CameraCapability extends HomeSecurityThingCapability {
super.updateWebhookEvent(event); super.updateWebhookEvent(event);
if (hasSubEventGroup) { if (hasSubEventGroup) {
updateSubGroup(event, thing.getUID(), GROUP_SUB_EVENT); updateSubGroup(event, GROUP_SUB_EVENT);
} }
if (hasLastEventGroup) { if (hasLastEventGroup) {
updateSubGroup(event, thing.getUID(), GROUP_LAST_EVENT); updateSubGroup(event, GROUP_LAST_EVENT);
} }
// The channel should get triggered at last (after super and sub methods), because this allows rules to access // The channel should get triggered at last (after super and sub methods), because this allows rules to access
@ -111,19 +120,17 @@ public class CameraCapability extends HomeSecurityThingCapability {
handler.triggerChannel(CHANNEL_HOME_EVENT, eventType); handler.triggerChannel(CHANNEL_HOME_EVENT, eventType);
} }
private void updateSubGroup(WebhookEvent event, ThingUID thingUid, String group) { private void updateSubGroup(WebhookEvent event, String group) {
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_TYPE), toStringType(event.getEventType())); handler.updateState(group, CHANNEL_EVENT_TYPE, toStringType(event.getEventType()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_TIME), toDateTimeType(event.getTime())); handler.updateState(group, CHANNEL_EVENT_TIME, toDateTimeType(event.getTime()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_SNAPSHOT), toRawType(event.getSnapshotUrl())); handler.updateState(group, CHANNEL_EVENT_SNAPSHOT, toRawType(event.getSnapshotUrl()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_SNAPSHOT_URL), handler.updateState(group, CHANNEL_EVENT_SNAPSHOT_URL, toStringType(event.getSnapshotUrl()));
toStringType(event.getSnapshotUrl())); handler.updateState(group, CHANNEL_EVENT_VIGNETTE, toRawType(event.getVignetteUrl()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_VIGNETTE), toRawType(event.getVignetteUrl())); handler.updateState(group, CHANNEL_EVENT_VIGNETTE_URL, toStringType(event.getVignetteUrl()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_VIGNETTE_URL), handler.updateState(group, CHANNEL_EVENT_SUBTYPE,
toStringType(event.getVignetteUrl()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_SUBTYPE),
event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL)); event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL));
final String message = event.getName(); final String message = event.getName();
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_MESSAGE), handler.updateState(group, CHANNEL_EVENT_MESSAGE,
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message)); message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
State personId = event.getPersons().isEmpty() ? UnDefType.NULL State personId = event.getPersons().isEmpty() ? UnDefType.NULL
: toStringType(event.getPersons().values().iterator().next().getId()); : toStringType(event.getPersons().values().iterator().next().getId());
@ -161,4 +168,31 @@ public class CameraCapability extends HomeSecurityThingCapability {
}); });
return result; return result;
} }
public @Nullable String ping(String vpnUrl) {
return getSecurityCapability().map(cap -> {
UriBuilder builder = UriBuilder.fromPath(cap.ping(vpnUrl));
URI apiLocalUrl = null;
try {
apiLocalUrl = builder.build();
if (apiLocalUrl.getHost().startsWith("169.254.")) {
logger.warn("Suspicious local IP address received: {}", apiLocalUrl);
Configuration config = handler.getThing().getConfiguration();
if (config.containsKey(IP_ADDRESS)) {
String provided = (String) config.get(IP_ADDRESS);
apiLocalUrl = builder.host(provided).build();
logger.info("Using {} as local url for '{}'", apiLocalUrl, thingUID);
} else {
logger.debug("No alternative ip Address provided, keeping API answer");
}
}
} catch (UriBuilderException e) { // Crashed at first URI build
logger.warn("API returned a badly formatted local url address for '{}': {}", thingUID, e.getMessage());
} catch (IllegalArgumentException e) {
logger.warn("Invalid fallback address provided in configuration for '{}' keeping API answer: {}",
thingUID, e.getMessage());
}
return apiLocalUrl != null ? apiLocalUrl.toString() : null;
}).orElse(null);
}
} }

View File

@ -36,6 +36,7 @@ import org.openhab.binding.netatmo.internal.api.dto.NAThing;
import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent; import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent;
import org.openhab.binding.netatmo.internal.handler.CommonInterface; import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
@ -50,6 +51,7 @@ public class Capability {
protected final Thing thing; protected final Thing thing;
protected final CommonInterface handler; protected final CommonInterface handler;
protected final ModuleType moduleType; protected final ModuleType moduleType;
protected final ThingUID thingUID;
protected boolean firstLaunch; protected boolean firstLaunch;
protected Map<String, String> properties = Map.of(); protected Map<String, String> properties = Map.of();
@ -58,6 +60,7 @@ public class Capability {
Capability(CommonInterface handler) { Capability(CommonInterface handler) {
this.handler = handler; this.handler = handler;
this.thing = handler.getThing(); this.thing = handler.getThing();
this.thingUID = thing.getUID();
this.moduleType = ModuleType.from(thing.getThingTypeUID()); this.moduleType = ModuleType.from(thing.getThingTypeUID());
} }

View File

@ -59,7 +59,7 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
@Override @Override
public void initialize() { public void initialize() {
super.initialize(); super.initialize();
energyId = handler.getConfiguration().as(HomeConfiguration.class).getIdForArea(FeatureArea.ENERGY); energyId = handler.getThingConfigAs(HomeConfiguration.class).getIdForArea(FeatureArea.ENERGY);
} }
@Override @Override
@ -77,7 +77,7 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule)); .forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
}); });
}); });
descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), GROUP_ENERGY, CHANNEL_PLANNING), descriptionProvider.setStateOptions(new ChannelUID(thingUID, GROUP_ENERGY, CHANNEL_PLANNING),
energyData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName())).toList()); energyData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName())).toList());
setPointDefaultDuration = energyData.getThermSetpointDefaultDuration(); setPointDefaultDuration = energyData.getThermSetpointDefaultDuration();
} }

View File

@ -58,7 +58,7 @@ public class HomeCapability extends RestCapability<HomeApi> {
@Override @Override
public void initialize() { public void initialize() {
super.initialize(); super.initialize();
HomeConfiguration config = handler.getConfiguration().as(HomeConfiguration.class); HomeConfiguration config = handler.getThingConfigAs(HomeConfiguration.class);
homeIds.add(config.getId()); homeIds.add(config.getId());
if (!config.energyId.isBlank()) { if (!config.energyId.isBlank()) {
homeIds.add(config.energyId); homeIds.add(config.energyId);

View File

@ -34,7 +34,6 @@ import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.StateOption; import org.openhab.core.types.StateOption;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
@ -53,7 +52,7 @@ public class PersonCapability extends HomeSecurityThingCapability {
public PersonCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider, public PersonCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
List<ChannelHelper> channelHelpers) { List<ChannelHelper> channelHelpers) {
super(handler, descriptionProvider, channelHelpers); super(handler, descriptionProvider, channelHelpers);
this.cameraChannelUID = new ChannelUID(thing.getUID(), GROUP_PERSON_LAST_EVENT, CHANNEL_EVENT_CAMERA_ID); this.cameraChannelUID = new ChannelUID(thingUID, GROUP_PERSON_LAST_EVENT, CHANNEL_EVENT_CAMERA_ID);
} }
@Override @Override
@ -78,21 +77,15 @@ public class PersonCapability extends HomeSecurityThingCapability {
protected void updateWebhookEvent(WebhookEvent event) { protected void updateWebhookEvent(WebhookEvent event) {
super.updateWebhookEvent(event); super.updateWebhookEvent(event);
ThingUID thingUid = thing.getUID(); handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE,
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE),
event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL)); event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL));
final String message = event.getName(); final String message = event.getName();
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_MESSAGE), handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_MESSAGE,
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message)); message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_TIME), handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_TIME, toDateTimeType(event.getTime()));
toDateTimeType(event.getTime())); handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SNAPSHOT, toRawType(event.getSnapshotUrl()));
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_SNAPSHOT),
toRawType(event.getSnapshotUrl()));
handler.updateState(cameraChannelUID, toStringType(event.getCameraId())); handler.updateState(cameraChannelUID, toStringType(event.getCameraId()));
} }

View File

@ -65,7 +65,7 @@ class SecurityCapability extends RestCapability<SecurityApi> {
public void initialize() { public void initialize() {
super.initialize(); super.initialize();
freshestEventTime = null; freshestEventTime = null;
securityId = handler.getConfiguration().as(HomeConfiguration.class).getIdForArea(FeatureArea.SECURITY); securityId = handler.getThingConfigAs(HomeConfiguration.class).getIdForArea(FeatureArea.SECURITY);
} }
@Override @Override

View File

@ -85,7 +85,7 @@ public class CameraChannelHelper extends ChannelHelper {
if (!isMonitoring || (local && !isLocal) || url == null) { if (!isMonitoring || (local && !isLocal) || url == null) {
return null; return null;
} }
return String.format("%s%s", url, LIVE_PICTURE); return "%s%s".formatted(url, LIVE_PICTURE);
} }
private State getLiveStreamURL(boolean local, @Nullable String configQual, boolean isMonitoring) { private State getLiveStreamURL(boolean local, @Nullable String configQual, boolean isMonitoring) {
@ -94,6 +94,6 @@ public class CameraChannelHelper extends ChannelHelper {
return UnDefType.NULL; return UnDefType.NULL;
} }
String finalQual = configQual != null ? configQual : "poor"; String finalQual = configQual != null ? configQual : "poor";
return toStringType("%s/live/%s", url, local ? String.format("files/%s/index.m3u8", finalQual) : "index.m3u8"); return toStringType("%s/live/%s", url, local ? "files/%s/index.m3u8".formatted(finalQual) : "index.m3u8");
} }
} }

View File

@ -77,7 +77,7 @@ public class NetatmoThingTypeProvider implements ThingTypeProvider {
.withExtensibleChannelTypeIds(moduleType.getExtensions()) .withExtensibleChannelTypeIds(moduleType.getExtensions())
.withChannelGroupDefinitions(getGroupDefinitions(moduleType)) .withChannelGroupDefinitions(getGroupDefinitions(moduleType))
.withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, moduleType.thingTypeVersion)) .withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, moduleType.thingTypeVersion))
.withConfigDescriptionURI(moduleType.getConfigDescription()); .withConfigDescriptionURI(moduleType.configDescription);
ThingTypeUID bridgeType = moduleType.getBridge().thingTypeUID; ThingTypeUID bridgeType = moduleType.getBridge().thingTypeUID;
if (!ModuleType.UNKNOWN.thingTypeUID.equals(bridgeType)) { if (!ModuleType.UNKNOWN.thingTypeUID.equals(bridgeType)) {

View File

@ -103,6 +103,18 @@
</parameter> </parameter>
</config-description> </config-description>
<config-description uri="netatmo:camera">
<parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
<label>@text/config.equipmentId.label</label>
<description>@text/config.equipmentId.description</description>
</parameter>
<parameter name="ipAddress" type="text" required="false">
<label>@text/config.ipAddress.label</label>
<description>@text/config.ipAddress.description</description>
<context>network-address</context>
</parameter>
</config-description>
<config-description uri="netatmo:virtual"> <config-description uri="netatmo:virtual">
<parameter name="id" type="text" required="true"> <parameter name="id" type="text" required="true">
<label>@text/config.thingId.label</label> <label>@text/config.thingId.label</label>

View File

@ -444,6 +444,8 @@ config.reconnectInterval.label = Reconnect Interval
config.reconnectInterval.description = The reconnection interval to Netatmo API (in s). config.reconnectInterval.description = The reconnection interval to Netatmo API (in s).
config.equipmentId.label = Equipment ID config.equipmentId.label = Equipment ID
config.equipmentId.description = ID of the device (MAC address). config.equipmentId.description = ID of the device (MAC address).
config.ipAddress.label = Network Address
config.ipAddress.description = The IP or host name used as fallback in case of erroneous API answer to ping requests.
config.thingId.label = Thing ID config.thingId.label = Thing ID
config.thingId.description = Unique identifier of the thing defined by Netatmo. config.thingId.description = Unique identifier of the thing defined by Netatmo.
config.securityId.label = Security ID config.securityId.label = Security ID