]> git.basschouten.com Git - openhab-addons.git/commitdiff
[netatmo] Enhance RefreshCapability (#16574)
authorGaël L'hopital <gael@lhopital.org>
Fri, 29 Mar 2024 08:07:36 +0000 (09:07 +0100)
committerGitHub <noreply@github.com>
Fri, 29 Mar 2024 08:07:36 +0000 (09:07 +0100)
Signed-off-by: clinique <gael@lhopital.org>
Signed-off-by: gael@lhopital.org <gael@lhopital.org>
15 files changed:
bundles/org.openhab.binding.netatmo/README.md
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoHandlerFactory.java
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/config/HomeConfiguration.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NAThingConfiguration.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/CacheCapability.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/CapabilityMap.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/ParentUpdateCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RefreshAutoCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RefreshCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/WeatherCapability.java
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/config/config.xml
bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/data/ModuleTypeTest.java

index 3bece02857a390b99c2f41eea4543ea794fc9946..9fa7a8fac7a6e50826387e07c179dcf895357a1b 100644 (file)
@@ -83,12 +83,12 @@ Once authentication process has been done, current refreshToken is stored in `/O
 | 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, 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                                                                        |
 | 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                                                                        |
@@ -161,8 +161,8 @@ If you did not manually create things in the *.things file, the Netatmo Binding
 
 ### Weather Station Main Indoor Device
 
-Weather station does not need any refreshInterval setting.
-Based on a standard update period of 10mn by Netatmo systems - it will auto adapt to stick closest as possible to last data availability.
+Weather station uses a default `refreshInterval` of 10 minutes (can be adjusted), based on a standard update period of Netatmo systems.
+It will auto-adapt to stick as closely as possible to the last data availability.
 
 **Supported channels for the main indoor module:**
 
@@ -330,6 +330,9 @@ All these channels are read only.
 
 ### Healthy Home Coach Device
 
+Home Coach uses a default `refreshInterval` of 10 minutes (can be adjusted), based on a standard update period of Netatmo systems.
+It will auto-adapt to stick as closely as possible to the last data availability.
+
 **Supported channels for the healthy home coach device:**
 
 | Channel Group | Channel Id          | Item Type            | Description                                      |
@@ -453,11 +456,12 @@ Depending on the way it is configured the behaviour will be adapted and availabl
 
 The Home thing has the following configuration elements:
 
-| Parameter  | Type   | Required | Description                                                                         |
-| ---------- | ------ | -------- | ----------------------------------------------------------------------------------- |
-| id (1)     | String | No       | If you have a single type of equipment, this id is to be used for the home          |
-| energyId   | String | No       | Id of a home holding energy control devices                                         |
-| securityId | String | No       | Id of a home holding security monitoring devices                                    |
+| Parameter       | Type    | Required | Description                                                                         |
+| --------------- | ------- | -------- | ----------------------------------------------------------------------------------- |
+| id (1)          | String  | No       | If you have a single type of equipment, this id is to be used for the home          |
+| energyId        | String  | No       | Id of a home holding energy control devices                                         |
+| securityId      | String  | No       | Id of a home holding security monitoring devices                                    |
+| refreshInterval | Integer | No       | Refresh interval for refreshing the data in seconds. Default 180.                   |
 
 At least one of these parameter must be filled - at most two : 
 
@@ -465,7 +469,7 @@ At least one of these parameter must be filled - at most two :
 * id or energyId
 * securityId and energyId
 
-(1) this parameter is only kept for backward compatibility.
+(1) this parameter is kept for backward compatibility.
 
 All channels are read only.
 
index 20e704292f709bc0a107091ce3a788b44352fa49..9af2f0ad26903acb193f047a1ab30bea256fd05a 100644 (file)
@@ -36,8 +36,11 @@ import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.DoorbellCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.ParentUpdateCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.RefreshAutoCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.RoomCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability;
 import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
@@ -148,6 +151,12 @@ public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
                 newCap = new MeasureCapability(handler, helpers);
             } else if (capability == ChannelHelperCapability.class) {
                 newCap = new ChannelHelperCapability(handler, helpers);
+            } else if (capability == RefreshAutoCapability.class) {
+                newCap = new RefreshAutoCapability(handler);
+            } else if (capability == RefreshCapability.class) {
+                newCap = new RefreshCapability(handler);
+            } else if (capability == ParentUpdateCapability.class) {
+                newCap = new ParentUpdateCapability(handler);
             }
             if (newCap != null) {
                 handler.getCapabilities().put(newCap);
index a1b201fb86f2527c0bd0b9109d0d698641559212..b1f4cbadf037b369710c982f82aa65d4cda30513 100644 (file)
@@ -34,8 +34,11 @@ import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.DoorbellCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.ParentUpdateCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.RefreshAutoCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.RoomCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability;
 import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityChannelHelper;
@@ -70,91 +73,100 @@ public enum ModuleType {
             new ChannelGroup(ApiBridgeChannelHelper.class, GROUP_MONITORING)),
 
     HOME(FeatureArea.NONE, "NAHome", 1, "home", ACCOUNT,
-            Set.of(DeviceCapability.class, HomeCapability.class, ChannelHelperCapability.class),
+            Set.of(DeviceCapability.class, HomeCapability.class, ChannelHelperCapability.class,
+                    RefreshCapability.class),
             new ChannelGroup(SecurityChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_SECURITY),
             new ChannelGroup(EnergyChannelHelper.class, GROUP_ENERGY)),
 
     PERSON(FeatureArea.SECURITY, "NAPerson", 1, "virtual", HOME,
-            Set.of(PersonCapability.class, ChannelHelperCapability.class),
+            Set.of(PersonCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.class),
             new ChannelGroup(PersonChannelHelper.class, GROUP_PERSON),
             new ChannelGroup(EventPersonChannelHelper.class, GROUP_PERSON_LAST_EVENT)),
 
     WELCOME(FeatureArea.SECURITY, "NACamera", 1, "camera", HOME,
-            Set.of(CameraCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, ChannelGroup.EVENT,
+            Set.of(CameraCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.class),
+            ChannelGroup.SIGNAL, ChannelGroup.EVENT,
             new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE)),
 
-    TAG(FeatureArea.SECURITY, "NACamDoorTag", 1, "device", 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, ParentUpdateCapability.class), ChannelGroup.SIGNAL,
+            ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(DoorTagChannelHelper.class, GROUP_TAG)),
 
-    SIREN(FeatureArea.SECURITY, "NIS", 1, "device", WELCOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
+    SIREN(FeatureArea.SECURITY, "NIS", 1, "device", WELCOME,
+            Set.of(ChannelHelperCapability.class, ParentUpdateCapability.class), ChannelGroup.SIGNAL,
             ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(SirenChannelHelper.class, GROUP_SIREN)),
 
     PRESENCE(FeatureArea.SECURITY, "NOC", 1, "camera", HOME,
-            Set.of(PresenceCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, ChannelGroup.EVENT,
+            Set.of(PresenceCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.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, "camera", HOME,
-            Set.of(DoorbellCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
+            Set.of(DoorbellCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.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, "device", ACCOUNT,
+    WEATHER_STATION(FeatureArea.WEATHER, "NAMain", 1, "weather", ACCOUNT,
             Set.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class,
-                    ChannelHelperCapability.class),
+                    ChannelHelperCapability.class, RefreshAutoCapability.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, "device", WEATHER_STATION,
-            Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, ChannelGroup.HUMIDITY,
-            ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.TEMP_OUTSIDE_EXT),
+            Set.of(MeasureCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.class),
+            ChannelGroup.SIGNAL, ChannelGroup.HUMIDITY, ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE,
+            ChannelGroup.BATTERY, ChannelGroup.TEMP_OUTSIDE_EXT),
 
-    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)),
+    WIND(FeatureArea.WEATHER, "NAModule2", 1, "device", WEATHER_STATION,
+            Set.of(ChannelHelperCapability.class, ParentUpdateCapability.class), ChannelGroup.SIGNAL,
+            ChannelGroup.TSTAMP_EXT, ChannelGroup.BATTERY, new ChannelGroup(WindChannelHelper.class, GROUP_WIND)),
 
     RAIN(FeatureArea.WEATHER, "NAModule3", 1, "device", WEATHER_STATION,
-            Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
-            ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY,
+            Set.of(MeasureCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.class),
+            ChannelGroup.SIGNAL, ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY,
             new ChannelGroup(RainChannelHelper.class, MeasureClass.RAIN_QUANTITY, GROUP_RAIN)),
 
     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),
+            Set.of(MeasureCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.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, "device", ACCOUNT,
+    HOME_COACH(FeatureArea.AIR_CARE, "NHC", 1, "weather", ACCOUNT,
             Set.of(DeviceCapability.class, AirCareCapability.class, MeasureCapability.class,
-                    ChannelHelperCapability.class),
+                    ChannelHelperCapability.class, RefreshAutoCapability.class),
             ChannelGroup.LOCATION, ChannelGroup.SIGNAL, ChannelGroup.NOISE, ChannelGroup.HUMIDITY,
             ChannelGroup.TEMP_INSIDE, ChannelGroup.MEASURE, ChannelGroup.TSTAMP_EXT,
             new ChannelGroup(AirQualityChannelHelper.class, GROUP_TYPE_AIR_QUALITY_EXTENDED),
             new ChannelGroup(PressureChannelHelper.class, MeasureClass.PRESSURE, GROUP_PRESSURE)),
 
-    PLUG(FeatureArea.ENERGY, "NAPlug", 1, "device", HOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL),
+    PLUG(FeatureArea.ENERGY, "NAPlug", 1, "device", HOME,
+            Set.of(ChannelHelperCapability.class, ParentUpdateCapability.class), ChannelGroup.SIGNAL),
 
-    VALVE(FeatureArea.ENERGY, "NRV", 1, "device", PLUG, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
+    VALVE(FeatureArea.ENERGY, "NRV", 1, "device", PLUG,
+            Set.of(ChannelHelperCapability.class, ParentUpdateCapability.class), ChannelGroup.SIGNAL,
             ChannelGroup.BATTERY_EXT),
 
-    THERMOSTAT(FeatureArea.ENERGY, "NATherm1", 1, "device", 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, ParentUpdateCapability.class), ChannelGroup.SIGNAL,
+            ChannelGroup.BATTERY_EXT, new ChannelGroup(Therm1ChannelHelper.class, GROUP_TYPE_TH_PROPERTIES)),
 
-    ROOM(FeatureArea.ENERGY, "NARoom", 1, "virtual", HOME, Set.of(RoomCapability.class, ChannelHelperCapability.class),
+    ROOM(FeatureArea.ENERGY, "NARoom", 1, "virtual", HOME,
+            Set.of(RoomCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.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, "device", HOME,
-            Set.of(AlarmEventCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
-            ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT),
+            Set.of(AlarmEventCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.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);
+            Set.of(AlarmEventCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.class),
+            ChannelGroup.SIGNAL, ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT);
 
     public static final EnumSet<ModuleType> AS_SET = EnumSet.allOf(ModuleType.class);
 
index efbb3b398f8a5207dbd6da226d1ed9bed9de57f0..3a44b50347c9e630c3fef72abb16ceaf4223b0d1 100644 (file)
@@ -31,6 +31,12 @@ public class HomeConfiguration extends NAThingConfiguration {
         return getIdForArea(energyId.isBlank() ? FeatureArea.SECURITY : FeatureArea.ENERGY);
     }
 
+    @Override
+    public int getRefreshInterval() {
+        int local = refreshInterval;
+        return local == -1 ? 180 : local;
+    }
+
     public String getIdForArea(FeatureArea feature) {
         return FeatureArea.ENERGY.equals(feature) ? energyId.isBlank() ? id : energyId
                 : FeatureArea.SECURITY.equals(feature) ? securityId.isBlank() ? id : securityId : id;
index 2fc3f58d9f0bc6349056a0e128c9752e1f1fa7bd..95c23e930eee4768b411320243b17452ffc559e2 100644 (file)
@@ -25,7 +25,12 @@ public class NAThingConfiguration {
     public static final String ID = "id";
 
     protected String id = "";
-    public int refreshInterval = -1;
+    protected int refreshInterval = -1;
+
+    public int getRefreshInterval() {
+        int local = refreshInterval;
+        return local == -1 ? 600 : local;
+    }
 
     public String getId() {
         return id;
index da571ce7a857257c98ee82c75f5bdc4ad8a26c8d..51beb8de5ccd8fb8e4464f83ec61848186e68baa 100644 (file)
  */
 package org.openhab.binding.netatmo.internal.handler;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -28,8 +31,6 @@ import org.openhab.binding.netatmo.internal.config.NAThingConfiguration;
 import org.openhab.binding.netatmo.internal.handler.capability.Capability;
 import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
 import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
-import org.openhab.binding.netatmo.internal.handler.capability.ParentUpdateCapability;
-import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability;
 import org.openhab.binding.netatmo.internal.handler.capability.RestCapability;
 import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.Channel;
@@ -87,6 +88,10 @@ public interface CommonInterface {
                 : null;
     }
 
+    default Optional<ScheduledFuture<?>> schedule(Runnable arg0, Duration delay) {
+        return Optional.of(getScheduler().schedule(arg0, delay.getSeconds(), TimeUnit.SECONDS));
+    }
+
     default @Nullable ApiBridgeHandler getAccountHandler() {
         Bridge bridge = getBridge();
         BridgeHandler bridgeHandler = null;
@@ -221,15 +226,14 @@ public interface CommonInterface {
             setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, null);
         } else if (!ThingStatus.ONLINE.equals(bridge.getStatus())) {
             setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
-            getCapabilities().remove(RefreshCapability.class);
-            getCapabilities().remove(ParentUpdateCapability.class);
+            getCapabilities().getParentUpdate().ifPresent(Capability::dispose);
+            getCapabilities().getRefresh().ifPresent(Capability::dispose);
         } else {
             setThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null);
-            if (ModuleType.ACCOUNT.equals(getModuleType().getBridge())) {
-                NAThingConfiguration config = getThing().getConfiguration().as(NAThingConfiguration.class);
-                getCapabilities().put(new RefreshCapability(this, config.refreshInterval));
-            }
-            getCapabilities().put(new ParentUpdateCapability(this));
+            getCapabilities().getParentUpdate().ifPresentOrElse(Capability::initialize, () -> {
+                int interval = getThingConfigAs(NAThingConfiguration.class).getRefreshInterval();
+                getCapabilities().getRefresh().ifPresent(cap -> cap.setInterval(Duration.ofSeconds(interval)));
+            });
         }
     }
 
index b2ecd79482f907fa5a188098011cf3f5b68e4e8f..88561ccd4035af111494e44706a5a24a8a2bf6d0 100644 (file)
@@ -46,7 +46,7 @@ public abstract class CacheCapability<T extends RestManager> extends RestCapabil
     protected synchronized List<NAObject> updateReadings(T api) {
         Instant now = Instant.now();
 
-        if (requestTS.plus(validity).isBefore(now)) {
+        if (!stillValid(now)) {
             logger.debug("{} requesting fresh data for {}", getClass().getSimpleName(), thingUID);
             List<NAObject> result = getFreshData(api);
             if (!result.isEmpty()) {
@@ -58,5 +58,9 @@ public abstract class CacheCapability<T extends RestManager> extends RestCapabil
         return lastResult;
     }
 
+    protected boolean stillValid(Instant ts) {
+        return requestTS.plus(validity).isAfter(ts);
+    }
+
     protected abstract List<NAObject> getFreshData(T api);
 }
index 3ca3a89b0fa093f93fc86bfc866a8656a38597cd..749a6817e878b4689b3e0d99a602e7b520d2ad6c 100644 (file)
@@ -153,7 +153,7 @@ public class Capability {
 
     public void expireData() {
         CommonInterface bridgeHandler = handler.getBridgeHandler();
-        if (bridgeHandler != null && !handler.getCapabilities().containsKey(RefreshCapability.class)) {
+        if (bridgeHandler != null && handler.getCapabilities().getRefresh().isEmpty()) {
             bridgeHandler.expireData();
         }
     }
index 8b7517cc470308c4b3a4c35ce27298ea04c37b5a..301edc53819641d561fc2c560790a1dc9525c660 100644 (file)
@@ -49,4 +49,13 @@ public class CapabilityMap extends ConcurrentHashMap<Class<?>, Capability> {
             cap.dispose();
         }
     }
+
+    public Optional<RefreshCapability> getRefresh() {
+        return values().stream().filter(RefreshCapability.class::isInstance).map(RefreshCapability.class::cast)
+                .findFirst();
+    }
+
+    public Optional<Capability> getParentUpdate() {
+        return values().stream().filter(ParentUpdateCapability.class::isInstance).findFirst();
+    }
 }
index 9e36f1aba12c5f14c8d26d41844c17863fef0ed6..28a1b6ea71694a760f72208492242b359eddb81c 100644 (file)
@@ -12,9 +12,9 @@
  */
 package org.openhab.binding.netatmo.internal.handler.capability;
 
+import java.time.Duration;
 import java.util.Optional;
 import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.netatmo.internal.handler.CommonInterface;
@@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory;
  */
 @NonNullByDefault
 public class ParentUpdateCapability extends Capability {
-    private static final int DEFAULT_DELAY_S = 2;
+    private static final Duration DEFAULT_DELAY = Duration.ofSeconds(2);
 
     private final Logger logger = LoggerFactory.getLogger(ParentUpdateCapability.class);
     private Optional<ScheduledFuture<?>> job = Optional.empty();
@@ -40,13 +40,13 @@ public class ParentUpdateCapability extends Capability {
 
     @Override
     public void initialize() {
-        job = Optional.of(handler.getScheduler().schedule(() -> {
-            logger.debug("Requesting parents data update for Thing {}", handler.getId());
+        job = handler.schedule(() -> {
+            logger.debug("Requesting parents data update for Thing '{}'", thingUID);
             CommonInterface bridgeHandler = handler.getBridgeHandler();
             if (bridgeHandler != null) {
                 bridgeHandler.expireData();
             }
-        }, DEFAULT_DELAY_S, TimeUnit.SECONDS));
+        }, DEFAULT_DELAY);
     }
 
     @Override
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RefreshAutoCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RefreshAutoCapability.java
new file mode 100644 (file)
index 0000000..d230df5
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link RefreshAutoCapability} implements probing and auto-adjusting refresh strategy
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class RefreshAutoCapability extends RefreshCapability {
+    private static final Duration DEFAULT_DELAY = Duration.ofSeconds(15);
+
+    private final Logger logger = LoggerFactory.getLogger(RefreshAutoCapability.class);
+
+    private Instant dataTimeStamp = Instant.MIN;
+
+    public RefreshAutoCapability(CommonInterface handler) {
+        super(handler);
+    }
+
+    @Override
+    public void expireData() {
+        dataTimeStamp = Instant.MIN;
+        super.expireData();
+    }
+
+    @Override
+    protected Duration calcDelay() {
+        if (Instant.MIN.equals(dataTimeStamp)) {
+            return PROBING_INTERVAL;
+        }
+
+        Duration dataAge = Duration.between(dataTimeStamp, Instant.now());
+
+        Duration delay = dataValidity.minus(dataAge);
+        if (delay.isNegative() || delay.isZero()) {
+            logger.debug("{} did not update data in expected time, return to probing", thingUID);
+            dataTimeStamp = Instant.MIN;
+            return PROBING_INTERVAL;
+        }
+
+        return delay.plus(DEFAULT_DELAY);
+    }
+
+    @Override
+    protected void updateNAThing(NAThing newData) {
+        super.updateNAThing(newData);
+        dataTimeStamp = newData.getLastSeen().map(ZonedDateTime::toInstant).orElse(Instant.MIN);
+    }
+
+    @Override
+    protected void afterNewData(@Nullable NAObject newData) {
+        properties.put("probing", Boolean.valueOf(Instant.MIN.equals(dataTimeStamp)).toString());
+        super.afterNewData(newData);
+    }
+}
index 26aba9e1c71d84a4dd34136b7f3dcff5181f5151..59a1348afce40d94c42250a5e12bf686d1986c60 100644 (file)
  */
 package org.openhab.binding.netatmo.internal.handler.capability;
 
-import static java.time.temporal.ChronoUnit.*;
-
 import java.time.Duration;
 import java.time.Instant;
-import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.Optional;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
 import org.openhab.binding.netatmo.internal.handler.CommonInterface;
 import org.openhab.core.thing.ThingStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * {@link RefreshCapability} is the class used to embed the refreshing needs calculation for devices
+ * {@link RefreshCapability} is the base class used to define refreshing policies
+ * It implements of a fixed refresh rate strategy.
  *
  * @author Gaël L'hopital - Initial contribution
  *
  */
 @NonNullByDefault
 public class RefreshCapability extends Capability {
-    private static final Duration DEFAULT_DELAY = Duration.of(20, SECONDS);
-    private static final Duration PROBING_INTERVAL = Duration.of(120, SECONDS);
-    private static final Duration OFFLINE_INTERVAL = Duration.of(15, MINUTES);
+    protected static final Duration ASAP = Duration.ofSeconds(2);
+    protected static final Duration OFFLINE_DELAY = Duration.ofMinutes(15);
+    protected static final Duration PROBING_INTERVAL = Duration.ofMinutes(2);
 
     private final Logger logger = LoggerFactory.getLogger(RefreshCapability.class);
 
-    private Duration dataValidity;
-    private Instant dataTimeStamp = Instant.now();
-    private Instant dataTimeStamp0 = Instant.MIN;
+    protected Duration dataValidity = PROBING_INTERVAL;
     private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
-    private boolean refreshConfigured;
+    private boolean expiring = false;
 
-    public RefreshCapability(CommonInterface handler, int refreshInterval) {
+    public RefreshCapability(CommonInterface handler) {
         super(handler);
-        this.dataValidity = Duration.ofSeconds(Math.max(0, refreshInterval));
     }
 
-    @Override
-    public void initialize() {
-        this.refreshConfigured = !probing();
-        freeJobAndReschedule(2);
+    public void setInterval(Duration dataValidity) {
+        if (dataValidity.isNegative() || dataValidity.isZero()) {
+            throw new IllegalArgumentException("refreshInterval must be positive");
+        }
+        this.dataValidity = dataValidity;
+        expireData();
     }
 
     @Override
     public void dispose() {
-        freeJobAndReschedule(0);
+        stopJob();
         super.dispose();
     }
 
     @Override
     public void expireData() {
-        dataTimeStamp = Instant.now().minus(dataValidity);
-        freeJobAndReschedule(1);
+        if (!expiring) {
+            expiring = true;
+            rescheduleJob(ASAP);
+        }
     }
 
-    private Duration dataAge() {
-        return Duration.between(dataTimeStamp, Instant.now());
+    @Override
+    protected void afterNewData(@Nullable NAObject newData) {
+        expiring = false;
+        super.afterNewData(newData);
     }
 
-    private boolean probing() {
-        return dataValidity.getSeconds() <= 0;
+    protected Duration calcDelay() {
+        return dataValidity;
     }
 
     private void proceedWithUpdate() {
+        Duration delay;
         handler.proceedWithUpdate();
-        long delay;
         if (!ThingStatus.ONLINE.equals(handler.getThing().getStatus())) {
-            logger.debug("{} is not ONLINE, special refresh interval is used", thingUID);
-            delay = OFFLINE_INTERVAL.toSeconds();
-            if (probing()) {
-                dataTimeStamp0 = Instant.MIN;
-            }
+            delay = OFFLINE_DELAY;
+            logger.debug("Thing '{}' is not ONLINE, using special refresh interval", thingUID);
         } else {
-            delay = refreshConfigured ? dataValidity.getSeconds()
-                    : (probing() ? PROBING_INTERVAL : dataValidity.minus(dataAge()).plus(DEFAULT_DELAY)).toSeconds();
+            delay = calcDelay();
         }
-        delay = delay < 2 ? PROBING_INTERVAL.toSeconds() : delay;
-        logger.debug("{} refreshed, next one in {}s", thingUID, delay);
-        freeJobAndReschedule(delay);
+        rescheduleJob(delay);
     }
 
-    @Override
-    protected void updateNAThing(NAThing newData) {
-        super.updateNAThing(newData);
-        newData.getLastSeen().map(ZonedDateTime::toInstant).ifPresent(tsInstant -> {
-            if (probing()) {
-                if (Instant.MIN.equals(dataTimeStamp0)) {
-                    dataTimeStamp0 = tsInstant;
-                    logger.debug("First data timestamp of {} is {}", thingUID, dataTimeStamp0);
-                } else if (tsInstant.isAfter(dataTimeStamp0)) {
-                    dataValidity = Duration.between(dataTimeStamp0, tsInstant);
-                    refreshConfigured = true;
-                    logger.debug("Data validity period of {} identified to be {}", thingUID, dataValidity);
-                } else {
-                    logger.debug("Data validity period of {} not yet found, data timestamp unchanged", thingUID);
-                }
+    private void rescheduleJob(Duration delay) {
+        if (refreshJob.isPresent()) {
+            ScheduledFuture<?> job = refreshJob.get();
+            Instant now = Instant.now();
+            Instant expectedExecution = now.plus(delay);
+            Instant scheduledExecution = now.plusMillis(job.getDelay(TimeUnit.MILLISECONDS));
+            if (Math.abs(ChronoUnit.SECONDS.between(expectedExecution, scheduledExecution)) <= 3) {
+                logger.debug("'{}' refresh as already pending roughly as the same time, will not reschedule", thingUID);
+                return;
+            } else {
+                stopJob();
             }
-            dataTimeStamp = tsInstant;
-        });
+        }
+        logger.debug("'{}' next refresh in {}", thingUID, delay);
+        refreshJob = handler.schedule(this::proceedWithUpdate, delay);
     }
 
-    private void freeJobAndReschedule(long delay) {
+    private void stopJob() {
         refreshJob.ifPresent(job -> job.cancel(true));
-        refreshJob = Optional.ofNullable(delay == 0 ? null
-                : handler.getScheduler().schedule(() -> proceedWithUpdate(), delay, TimeUnit.SECONDS));
+        refreshJob = Optional.empty();
     }
 }
index 4789af458773a42270fad6d10fcec670177d2b2b..72a5bdda6d0ec5070c747b1891f32621c144b7f3 100644 (file)
@@ -34,7 +34,7 @@ public class WeatherCapability extends CacheCapability<WeatherApi> {
     private final Logger logger = LoggerFactory.getLogger(WeatherCapability.class);
 
     public WeatherCapability(CommonInterface handler) {
-        super(handler, Duration.ofSeconds(2), WeatherApi.class);
+        super(handler, Duration.ofSeconds(10), WeatherApi.class);
     }
 
     @Override
index 40fb6f0f868ec6e4df8538c019c947d76085bd6f..e7866d83ae6e7ae33230a1a12a12a728f07d99e2 100644 (file)
                </parameter>
        </config-description>
 
+       <config-description uri="netatmo:weather">
+               <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="refreshInterval" type="integer" min="120" unit="s">
+                       <label>@text/config.refreshInterval.label</label>
+                       <description>@text/config.refreshInterval.description</description>
+                       <default>600</default>
+                       <advanced>true</advanced>
+               </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>
                        <advanced>true</advanced>
                </parameter>
 
-               <parameter name="refreshInterval" type="integer" min="20" unit="s">
+               <parameter name="refreshInterval" type="integer" min="60" unit="s">
                        <label>@text/config.refreshInterval.label</label>
                        <description>@text/config.refreshInterval.description</description>
                        <default>180</default>
index 3698a461527f7e05055d1c884de0de20e89da21b..b67bdb9d3ed7a73ffaca0d1541c2119374e14c7d 100644 (file)
@@ -30,6 +30,10 @@ public class ModuleTypeTest {
             // This did not exist prior to PR #16492
             return URI.create(BINDING_ID + ":camera");
         }
+        if (mt == ModuleType.WEATHER_STATION || mt == ModuleType.HOME_COACH) {
+            // This did not exist prior to PR #16492
+            return URI.create(BINDING_ID + ":weather");
+        }
         // This was previous method for calculating configuration URI
         return URI.create(BINDING_ID + ":"
                 + (mt == ModuleType.ACCOUNT ? "api_bridge"