]> git.basschouten.com Git - openhab-addons.git/commitdiff
[netatmo] Fix erroneous local URL handling (#16492)
authorGaël L'hopital <gael@lhopital.org>
Fri, 15 Mar 2024 10:24:27 +0000 (11:24 +0100)
committerGitHub <noreply@github.com>
Fri, 15 Mar 2024 10:24:27 +0000 (11:24 +0100)
* 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>
14 files changed:
bundles/org.openhab.binding.netatmo/README.md
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AlarmEventCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/Capability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PersonCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoThingTypeProvider.java
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/config/config.xml
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties

index 83b19cb45ae16f5756d22f9463c2200bc406aec9..7312a1de1366f41e329659c6b34a7b7b47e7fb95 100644 (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                                                          |
 | --------------- | ------ | -------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
 | 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                                                                        |
-| welcome         | Thing  | NACamera       | The Netatmo Smart Indoor Camera (Welcome).                                                            | id                                                                        |
-| presence        | Thing  | NOC            | The Netatmo Smart Outdoor Camera (Presence) camera with or without siren.                             | 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, ipAddress                                                             |
 | siren           | Thing  | NIS            | The Netatmo Smart Indoor Siren.                                                                       | id                                                                        |
-| doorbell        | Thing  | NDB            | The Netatmo Smart Video Doorbell device.                                                              | id                                                                        |
-| weather-station | Bridge | NAMain         | Main indoor module reporting temperature, humidity, pressure, air quality and sound level.            | 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, refreshInterval                                                       |
 | outdoor         | Thing  | NAModule1      | Outdoor module reporting temperature and humidity.                                                    | id                                                                        |
 | wind            | Thing  | NAModule2      | Wind sensor reporting wind angle and strength.                                                        | id                                                                        |
 | rain            | Thing  | NAModule3      | Rain Gauge measuring precipitation.                                                                   | 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                                                                        |
 | thermostat      | Thing  | NATherm1       | The Thermostat device placed in a given room.                                                         | id                                                                        |
 | room            | Thing  | NARoom         | A room in your house.                                                                                 | id                                                                        |
index 575bc05219ceb4b80fad56c4edbdee058a8b15cb..2ac81a0e8ab7a3e9e46247bf4d8fab5962f455ee 100644 (file)
@@ -64,67 +64,70 @@ import org.openhab.core.thing.ThingTypeUID;
  */
 @NonNullByDefault
 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),
             new ChannelGroup(SecurityChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_SECURITY),
             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(EventPersonChannelHelper.class, GROUP_PERSON_LAST_EVENT)),
 
-    WELCOME(FeatureArea.SECURITY, "NACamera", 1, HOME, Set.of(CameraCapability.class, ChannelHelperCapability.class),
-            ChannelGroup.SIGNAL, ChannelGroup.EVENT,
+    WELCOME(FeatureArea.SECURITY, "NACamera", 1, "camera", HOME,
+            Set.of(CameraCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, ChannelGroup.EVENT,
             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,
-            ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(DoorTagChannelHelper.class, GROUP_TAG)),
+    TAG(FeatureArea.SECURITY, "NACamDoorTag", 1, "device", WELCOME, Set.of(ChannelHelperCapability.class),
+            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)),
 
-    PRESENCE(FeatureArea.SECURITY, "NOC", 1, HOME, Set.of(PresenceCapability.class, ChannelHelperCapability.class),
-            ChannelGroup.SIGNAL, ChannelGroup.EVENT,
+    PRESENCE(FeatureArea.SECURITY, "NOC", 1, "camera", HOME,
+            Set.of(PresenceCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, ChannelGroup.EVENT,
             new ChannelGroup(PresenceChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE,
                     GROUP_PRESENCE),
             new ChannelGroup(EventCameraChannelHelper.class, GROUP_SUB_EVENT)),
 
-    DOORBELL(FeatureArea.SECURITY, "NDB", 1, HOME, Set.of(DoorbellCapability.class, ChannelHelperCapability.class),
-            ChannelGroup.SIGNAL,
+    DOORBELL(FeatureArea.SECURITY, "NDB", 1, "camera", HOME,
+            Set.of(DoorbellCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
             new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_DOORBELL_STATUS,
                     GROUP_DOORBELL_LIVE),
             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,
                     ChannelHelperCapability.class),
             ChannelGroup.SIGNAL, ChannelGroup.HUMIDITY, ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE,
             ChannelGroup.AIR_QUALITY, ChannelGroup.LOCATION, ChannelGroup.NOISE, ChannelGroup.TEMP_INSIDE_EXT,
             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,
             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,
             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,
             ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY,
             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,
             ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.HUMIDITY,
             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,
                     ChannelHelperCapability.class),
             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(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),
 
-    THERMOSTAT(FeatureArea.ENERGY, "NATherm1", 1, PLUG, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
-            ChannelGroup.BATTERY_EXT, new ChannelGroup(Therm1ChannelHelper.class, GROUP_TYPE_TH_PROPERTIES)),
+    THERMOSTAT(FeatureArea.ENERGY, "NATherm1", 1, "device", PLUG, Set.of(ChannelHelperCapability.class),
+            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(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,
             ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT),
 
-    CO_DETECTOR(FeatureArea.SECURITY, "NCO", 1, HOME, Set.of(AlarmEventCapability.class, ChannelHelperCapability.class),
-            ChannelGroup.SIGNAL, ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT);
+    CO_DETECTOR(FeatureArea.SECURITY, "NCO", 1, "device", HOME,
+            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);
 
@@ -160,8 +165,9 @@ public enum ModuleType {
     public final FeatureArea feature;
     public final String apiName;
     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) {
         this.bridgeType = Optional.ofNullable(bridge);
         this.feature = feature;
@@ -170,6 +176,7 @@ public enum ModuleType {
         this.channelGroups = Set.of(channelGroups);
         this.thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase().replace("_", "-"));
         this.thingTypeVersion = Integer.toString(thingTypeVersion);
+        this.configDescription = URI.create(BINDING_ID + ":" + config);
     }
 
     public boolean isLogical() {
@@ -203,13 +210,6 @@ public enum ModuleType {
         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() {
         ModuleType parent = getBridge();
         return parent == UNKNOWN ? 1 : parent.getDepth() + 1;
index bf1c8d5eb3c3031bd84de6d53760b13190f3af4b..59e43d3016d13cdcc3a859fc49a5a788e67a7208 100644 (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.RefreshCapability;
 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.Channel;
 import org.openhab.core.thing.ChannelUID;
@@ -69,6 +68,10 @@ public interface CommonInterface {
 
     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,
             @Nullable String thingStatusReason);
 
@@ -108,11 +111,11 @@ public interface CommonInterface {
     }
 
     default String getId() {
-        return getConfiguration().as(NAThingConfiguration.class).getId();
+        return getThingConfigAs(NAThingConfiguration.class).getId();
     }
 
-    default Configuration getConfiguration() {
-        return getThing().getConfiguration();
+    default <T> T getThingConfigAs(Class<T> configurationClass) {
+        return getThing().getConfiguration().as(configurationClass);
     }
 
     default Stream<Channel> getActiveChannels() {
index 4c559bcebaf307601209c7ac5688b37ce2311670..8e7c3a922bb86c6b099ee11999c5de63dd6f970b 100644 (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.channelhelper.ChannelHelper;
 import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.ThingUID;
+import org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils;
 import org.openhab.core.types.UnDefType;
 
 /**
@@ -45,15 +44,12 @@ public class AlarmEventCapability extends HomeSecurityThingCapability {
     protected void updateWebhookEvent(WebhookEvent event) {
         super.updateWebhookEvent(event);
 
-        final ThingUID thingUid = handler.getThing().getUID();
-        handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_TYPE),
-                toStringType(event.getEventType()));
-        handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_TIME),
-                toDateTimeType(event.getTime()));
-        handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE),
-                event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL));
+        handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_TYPE, toStringType(event.getEventType()));
+        handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_TIME, toDateTimeType(event.getTime()));
+        handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE,
+                event.getSubTypeDescription().map(ChannelTypeUtils::toStringType).orElse(UnDefType.NULL));
         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));
     }
 
index 72b9fb5a8f7f100e294c3d5323efafc37b8010b3..434ec81b31e4308f5134a5a5a3014d691bd43419 100644 (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.utils.ChannelTypeUtils.*;
 
+import java.net.URI;
 import java.util.ArrayList;
 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.Nullable;
 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.ChannelHelper;
 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.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.State;
 import org.openhab.core.types.StateOption;
 import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * {@link CameraCapability} give to handle Welcome Camera specifics
@@ -49,6 +54,10 @@ import org.openhab.core.types.UnDefType;
  */
 @NonNullByDefault
 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 ChannelUID personChannelUID;
 
@@ -60,7 +69,7 @@ public class CameraCapability extends HomeSecurityThingCapability {
     public CameraCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
             List<ChannelHelper> channelHelpers) {
         super(handler, descriptionProvider, channelHelpers);
-        this.personChannelUID = new ChannelUID(thing.getUID(), GROUP_LAST_EVENT, CHANNEL_EVENT_PERSON_ID);
+        this.personChannelUID = new ChannelUID(thingUID, GROUP_LAST_EVENT, CHANNEL_EVENT_PERSON_ID);
         this.cameraHelper = (CameraChannelHelper) channelHelpers.stream().filter(c -> c instanceof CameraChannelHelper)
                 .findFirst().orElseThrow(() -> new IllegalArgumentException(
                         "CameraCapability must find a CameraChannelHelper, please file a bug report."));
@@ -68,7 +77,6 @@ public class CameraCapability extends HomeSecurityThingCapability {
 
     @Override
     public void initialize() {
-        Thing thing = handler.getThing();
         hasSubEventGroup = !thing.getChannelsOfGroup(GROUP_SUB_EVENT).isEmpty();
         hasLastEventGroup = !thing.getChannelsOfGroup(GROUP_LAST_EVENT).isEmpty();
     }
@@ -80,14 +88,15 @@ public class CameraCapability extends HomeSecurityThingCapability {
         String newVpnUrl = newData.getVpnUrl();
         if (newVpnUrl != null && !newVpnUrl.equals(vpnUrl)) {
             // 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);
             eventHelper.setUrls(newVpnUrl, localUrl);
         }
         vpnUrl = newVpnUrl;
         if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus())
                 || !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);
 
         if (hasSubEventGroup) {
-            updateSubGroup(event, thing.getUID(), GROUP_SUB_EVENT);
+            updateSubGroup(event, GROUP_SUB_EVENT);
         }
 
         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
@@ -111,19 +120,17 @@ public class CameraCapability extends HomeSecurityThingCapability {
         handler.triggerChannel(CHANNEL_HOME_EVENT, eventType);
     }
 
-    private void updateSubGroup(WebhookEvent event, ThingUID thingUid, String group) {
-        handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_TYPE), toStringType(event.getEventType()));
-        handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_TIME), toDateTimeType(event.getTime()));
-        handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_SNAPSHOT), toRawType(event.getSnapshotUrl()));
-        handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_SNAPSHOT_URL),
-                toStringType(event.getSnapshotUrl()));
-        handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_VIGNETTE), toRawType(event.getVignetteUrl()));
-        handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_VIGNETTE_URL),
-                toStringType(event.getVignetteUrl()));
-        handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_SUBTYPE),
+    private void updateSubGroup(WebhookEvent event, String group) {
+        handler.updateState(group, CHANNEL_EVENT_TYPE, toStringType(event.getEventType()));
+        handler.updateState(group, CHANNEL_EVENT_TIME, toDateTimeType(event.getTime()));
+        handler.updateState(group, CHANNEL_EVENT_SNAPSHOT, toRawType(event.getSnapshotUrl()));
+        handler.updateState(group, CHANNEL_EVENT_SNAPSHOT_URL, toStringType(event.getSnapshotUrl()));
+        handler.updateState(group, CHANNEL_EVENT_VIGNETTE, toRawType(event.getVignetteUrl()));
+        handler.updateState(group, CHANNEL_EVENT_VIGNETTE_URL, toStringType(event.getVignetteUrl()));
+        handler.updateState(group, CHANNEL_EVENT_SUBTYPE,
                 event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL));
         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));
         State personId = event.getPersons().isEmpty() ? UnDefType.NULL
                 : toStringType(event.getPersons().values().iterator().next().getId());
@@ -161,4 +168,31 @@ public class CameraCapability extends HomeSecurityThingCapability {
         });
         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);
+    }
 }
index cea8018f4a3112ac748b51a88b9c5a1652657186..ec7eb2ea105cbf57d9f4908375065c83e4ac7bc8 100644 (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.handler.CommonInterface;
 import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingUID;
 import org.openhab.core.thing.binding.ThingHandlerService;
 import org.openhab.core.types.Command;
 
@@ -50,6 +51,7 @@ public class Capability {
     protected final Thing thing;
     protected final CommonInterface handler;
     protected final ModuleType moduleType;
+    protected final ThingUID thingUID;
 
     protected boolean firstLaunch;
     protected Map<String, String> properties = Map.of();
@@ -58,6 +60,7 @@ public class Capability {
     Capability(CommonInterface handler) {
         this.handler = handler;
         this.thing = handler.getThing();
+        this.thingUID = thing.getUID();
         this.moduleType = ModuleType.from(thing.getThingTypeUID());
     }
 
index e7ec8f931884e6988ef6219dbf7212288b957f8f..dad6236270a47e17466c7fd9c838d2f30cf60266 100644 (file)
@@ -59,7 +59,7 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
     @Override
     public void initialize() {
         super.initialize();
-        energyId = handler.getConfiguration().as(HomeConfiguration.class).getIdForArea(FeatureArea.ENERGY);
+        energyId = handler.getThingConfigAs(HomeConfiguration.class).getIdForArea(FeatureArea.ENERGY);
     }
 
     @Override
@@ -77,7 +77,7 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
                                     .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());
             setPointDefaultDuration = energyData.getThermSetpointDefaultDuration();
         }
index cc610ca64143642245c06eb94ecf4f7506323d62..a72f6aaf68dca7c99c8542fce86fac7de0548d27 100644 (file)
@@ -58,7 +58,7 @@ public class HomeCapability extends RestCapability<HomeApi> {
     @Override
     public void initialize() {
         super.initialize();
-        HomeConfiguration config = handler.getConfiguration().as(HomeConfiguration.class);
+        HomeConfiguration config = handler.getThingConfigAs(HomeConfiguration.class);
         homeIds.add(config.getId());
         if (!config.energyId.isBlank()) {
             homeIds.add(config.energyId);
index c09554a4f25a0b8bd4bc8f985a2df22b33be2a08..5f337af8959aefdf45772f08f7623a8c21f559cc 100644 (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.core.library.types.OnOffType;
 import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.ThingUID;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.StateOption;
 import org.openhab.core.types.UnDefType;
@@ -53,7 +52,7 @@ public class PersonCapability extends HomeSecurityThingCapability {
     public PersonCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
             List<ChannelHelper> 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
@@ -78,21 +77,15 @@ public class PersonCapability extends HomeSecurityThingCapability {
     protected void updateWebhookEvent(WebhookEvent event) {
         super.updateWebhookEvent(event);
 
-        ThingUID thingUid = thing.getUID();
-
-        handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE),
+        handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE,
                 event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL));
 
         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));
 
-        handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_TIME),
-                toDateTimeType(event.getTime()));
-
-        handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_SNAPSHOT),
-                toRawType(event.getSnapshotUrl()));
-
+        handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_TIME, toDateTimeType(event.getTime()));
+        handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SNAPSHOT, toRawType(event.getSnapshotUrl()));
         handler.updateState(cameraChannelUID, toStringType(event.getCameraId()));
     }
 
index 3ab10abc1313a709b6521f62700ccdae3cfc79ef..624606606ab041879bc437420c3729c3c8016e6c 100644 (file)
@@ -65,7 +65,7 @@ class SecurityCapability extends RestCapability<SecurityApi> {
     public void initialize() {
         super.initialize();
         freshestEventTime = null;
-        securityId = handler.getConfiguration().as(HomeConfiguration.class).getIdForArea(FeatureArea.SECURITY);
+        securityId = handler.getThingConfigAs(HomeConfiguration.class).getIdForArea(FeatureArea.SECURITY);
     }
 
     @Override
index 5d207abbd2bea65d0646b9de51c4877627a6daad..5e63cf85d790f6a426efb800acc627083c286eb2 100644 (file)
@@ -85,7 +85,7 @@ public class CameraChannelHelper extends ChannelHelper {
         if (!isMonitoring || (local && !isLocal) || url == 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) {
@@ -94,6 +94,6 @@ public class CameraChannelHelper extends ChannelHelper {
             return UnDefType.NULL;
         }
         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");
     }
 }
index fc61d4b2ab2776af078bff39496e13d32ed7e101..bf2079e66c20aa58d67c5a2041d8317f7e1cbc55 100644 (file)
@@ -77,7 +77,7 @@ public class NetatmoThingTypeProvider implements ThingTypeProvider {
                         .withExtensibleChannelTypeIds(moduleType.getExtensions())
                         .withChannelGroupDefinitions(getGroupDefinitions(moduleType))
                         .withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, moduleType.thingTypeVersion))
-                        .withConfigDescriptionURI(moduleType.getConfigDescription());
+                        .withConfigDescriptionURI(moduleType.configDescription);
 
                 ThingTypeUID bridgeType = moduleType.getBridge().thingTypeUID;
                 if (!ModuleType.UNKNOWN.thingTypeUID.equals(bridgeType)) {
index 808d58f4b1aaf95c2102a2ca8cb2886fd592920e..f1d88a60ecda948c026cf636173a2c2894278261 100644 (file)
                </parameter>
        </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">
                <parameter name="id" type="text" required="true">
                        <label>@text/config.thingId.label</label>
index 40aac14ddb3ec07ba4a0e67adfc96f0212c8a3a7..940b2d6f6911801303be996101d90b4436440585 100644 (file)
@@ -444,6 +444,8 @@ config.reconnectInterval.label = Reconnect Interval
 config.reconnectInterval.description = The reconnection interval to Netatmo API (in s).
 config.equipmentId.label = Equipment ID
 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.description = Unique identifier of the thing defined by Netatmo.
 config.securityId.label = Security ID