[netatmo] Enhance RefreshCapability (#16574)

Signed-off-by: clinique <gael@lhopital.org>
Signed-off-by: gael@lhopital.org <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2024-03-29 09:07:36 +01:00 committed by GitHub
parent 9f59d29c38
commit b6e3b816ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 271 additions and 128 deletions

View 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 | | 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, ipAddress | | 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 |
@ -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 Main Indoor Device
Weather station does not need any refreshInterval setting. Weather station uses a default `refreshInterval` of 10 minutes (can be adjusted), based on a standard update period of Netatmo systems.
Based on a standard update period of 10mn by Netatmo systems - it will auto adapt to stick closest as possible to last data availability. It will auto-adapt to stick as closely as possible to the last data availability.
**Supported channels for the main indoor module:** **Supported channels for the main indoor module:**
@ -330,6 +330,9 @@ All these channels are read only.
### Healthy Home Coach Device ### 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:** **Supported channels for the healthy home coach device:**
| Channel Group | Channel Id | Item Type | Description | | 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: The Home thing has the following configuration elements:
| Parameter | Type | Required | Description | | 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 | | 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 | | energyId | String | No | Id of a home holding energy control devices |
| securityId | String | No | Id of a home holding security monitoring 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 : 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 * id or energyId
* securityId and 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. All channels are read only.

View 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.DoorbellCapability;
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability; 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.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.PersonCapability;
import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability; 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.RoomCapability;
import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability; import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability;
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
@ -148,6 +151,12 @@ public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
newCap = new MeasureCapability(handler, helpers); newCap = new MeasureCapability(handler, helpers);
} else if (capability == ChannelHelperCapability.class) { } else if (capability == ChannelHelperCapability.class) {
newCap = new ChannelHelperCapability(handler, helpers); 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) { if (newCap != null) {
handler.getCapabilities().put(newCap); handler.getCapabilities().put(newCap);

View 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.DoorbellCapability;
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability; 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.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.PersonCapability;
import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability; 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.RoomCapability;
import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability; import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability;
import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityChannelHelper;
@ -70,91 +73,100 @@ public enum ModuleType {
new ChannelGroup(ApiBridgeChannelHelper.class, GROUP_MONITORING)), new ChannelGroup(ApiBridgeChannelHelper.class, GROUP_MONITORING)),
HOME(FeatureArea.NONE, "NAHome", 1, "home", ACCOUNT, 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(SecurityChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_SECURITY),
new ChannelGroup(EnergyChannelHelper.class, GROUP_ENERGY)), new ChannelGroup(EnergyChannelHelper.class, GROUP_ENERGY)),
PERSON(FeatureArea.SECURITY, "NAPerson", 1, "virtual", HOME, 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(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, "camera", HOME, 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)), new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE)),
TAG(FeatureArea.SECURITY, "NACamDoorTag", 1, "device", WELCOME, Set.of(ChannelHelperCapability.class), TAG(FeatureArea.SECURITY, "NACamDoorTag", 1, "device", WELCOME,
ChannelGroup.SIGNAL, ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, Set.of(ChannelHelperCapability.class, ParentUpdateCapability.class), ChannelGroup.SIGNAL,
new ChannelGroup(DoorTagChannelHelper.class, GROUP_TAG)), 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)), ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(SirenChannelHelper.class, GROUP_SIREN)),
PRESENCE(FeatureArea.SECURITY, "NOC", 1, "camera", HOME, 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, 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, "camera", HOME, 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, 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, "device", ACCOUNT, WEATHER_STATION(FeatureArea.WEATHER, "NAMain", 1, "weather", ACCOUNT,
Set.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class, Set.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class,
ChannelHelperCapability.class), ChannelHelperCapability.class, RefreshAutoCapability.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, "device", 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, ParentUpdateCapability.class),
ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.TEMP_OUTSIDE_EXT), 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), WIND(FeatureArea.WEATHER, "NAModule2", 1, "device", WEATHER_STATION,
ChannelGroup.SIGNAL, ChannelGroup.TSTAMP_EXT, ChannelGroup.BATTERY, Set.of(ChannelHelperCapability.class, ParentUpdateCapability.class), ChannelGroup.SIGNAL,
new ChannelGroup(WindChannelHelper.class, GROUP_WIND)), ChannelGroup.TSTAMP_EXT, ChannelGroup.BATTERY, new ChannelGroup(WindChannelHelper.class, GROUP_WIND)),
RAIN(FeatureArea.WEATHER, "NAModule3", 1, "device", WEATHER_STATION, RAIN(FeatureArea.WEATHER, "NAModule3", 1, "device", WEATHER_STATION,
Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, Set.of(MeasureCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.class),
ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.SIGNAL, 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, "device", WEATHER_STATION, INDOOR(FeatureArea.WEATHER, "NAModule4", 1, "device", WEATHER_STATION,
Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, Set.of(MeasureCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.class),
ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.HUMIDITY, ChannelGroup.SIGNAL, ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY,
ChannelGroup.TEMP_INSIDE_EXT, ChannelGroup.AIR_QUALITY), 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, Set.of(DeviceCapability.class, AirCareCapability.class, MeasureCapability.class,
ChannelHelperCapability.class), ChannelHelperCapability.class, RefreshAutoCapability.class),
ChannelGroup.LOCATION, ChannelGroup.SIGNAL, ChannelGroup.NOISE, ChannelGroup.HUMIDITY, ChannelGroup.LOCATION, ChannelGroup.SIGNAL, ChannelGroup.NOISE, ChannelGroup.HUMIDITY,
ChannelGroup.TEMP_INSIDE, ChannelGroup.MEASURE, ChannelGroup.TSTAMP_EXT, ChannelGroup.TEMP_INSIDE, ChannelGroup.MEASURE, ChannelGroup.TSTAMP_EXT,
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, "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), ChannelGroup.BATTERY_EXT),
THERMOSTAT(FeatureArea.ENERGY, "NATherm1", 1, "device", PLUG, Set.of(ChannelHelperCapability.class), THERMOSTAT(FeatureArea.ENERGY, "NATherm1", 1, "device", PLUG,
ChannelGroup.SIGNAL, ChannelGroup.BATTERY_EXT, Set.of(ChannelHelperCapability.class, ParentUpdateCapability.class), ChannelGroup.SIGNAL,
new ChannelGroup(Therm1ChannelHelper.class, GROUP_TYPE_TH_PROPERTIES)), 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(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, "device", HOME, SMOKE_DETECTOR(FeatureArea.SECURITY, "NSD", 1, "device", HOME,
Set.of(AlarmEventCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, Set.of(AlarmEventCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.class),
ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT), ChannelGroup.SIGNAL, ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT),
CO_DETECTOR(FeatureArea.SECURITY, "NCO", 1, "device", HOME, CO_DETECTOR(FeatureArea.SECURITY, "NCO", 1, "device", HOME,
Set.of(AlarmEventCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, Set.of(AlarmEventCapability.class, ChannelHelperCapability.class, ParentUpdateCapability.class),
ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT); 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);

View File

@ -31,6 +31,12 @@ public class HomeConfiguration extends NAThingConfiguration {
return getIdForArea(energyId.isBlank() ? FeatureArea.SECURITY : FeatureArea.ENERGY); 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) { public String getIdForArea(FeatureArea feature) {
return FeatureArea.ENERGY.equals(feature) ? energyId.isBlank() ? id : energyId return FeatureArea.ENERGY.equals(feature) ? energyId.isBlank() ? id : energyId
: FeatureArea.SECURITY.equals(feature) ? securityId.isBlank() ? id : securityId : id; : FeatureArea.SECURITY.equals(feature) ? securityId.isBlank() ? id : securityId : id;

View File

@ -25,7 +25,12 @@ public class NAThingConfiguration {
public static final String ID = "id"; public static final String ID = "id";
protected String 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() { public String getId() {
return id; return id;

View File

@ -12,10 +12,13 @@
*/ */
package org.openhab.binding.netatmo.internal.handler; package org.openhab.binding.netatmo.internal.handler;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; 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.Capability;
import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap; 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.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.binding.netatmo.internal.handler.capability.RestCapability;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel; import org.openhab.core.thing.Channel;
@ -87,6 +88,10 @@ public interface CommonInterface {
: null; : null;
} }
default Optional<ScheduledFuture<?>> schedule(Runnable arg0, Duration delay) {
return Optional.of(getScheduler().schedule(arg0, delay.getSeconds(), TimeUnit.SECONDS));
}
default @Nullable ApiBridgeHandler getAccountHandler() { default @Nullable ApiBridgeHandler getAccountHandler() {
Bridge bridge = getBridge(); Bridge bridge = getBridge();
BridgeHandler bridgeHandler = null; BridgeHandler bridgeHandler = null;
@ -221,15 +226,14 @@ public interface CommonInterface {
setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, null); setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, null);
} else if (!ThingStatus.ONLINE.equals(bridge.getStatus())) { } else if (!ThingStatus.ONLINE.equals(bridge.getStatus())) {
setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null); setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
getCapabilities().remove(RefreshCapability.class); getCapabilities().getParentUpdate().ifPresent(Capability::dispose);
getCapabilities().remove(ParentUpdateCapability.class); getCapabilities().getRefresh().ifPresent(Capability::dispose);
} else { } else {
setThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null); setThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null);
if (ModuleType.ACCOUNT.equals(getModuleType().getBridge())) { getCapabilities().getParentUpdate().ifPresentOrElse(Capability::initialize, () -> {
NAThingConfiguration config = getThing().getConfiguration().as(NAThingConfiguration.class); int interval = getThingConfigAs(NAThingConfiguration.class).getRefreshInterval();
getCapabilities().put(new RefreshCapability(this, config.refreshInterval)); getCapabilities().getRefresh().ifPresent(cap -> cap.setInterval(Duration.ofSeconds(interval)));
} });
getCapabilities().put(new ParentUpdateCapability(this));
} }
} }

View File

@ -46,7 +46,7 @@ public abstract class CacheCapability<T extends RestManager> extends RestCapabil
protected synchronized List<NAObject> updateReadings(T api) { protected synchronized List<NAObject> updateReadings(T api) {
Instant now = Instant.now(); Instant now = Instant.now();
if (requestTS.plus(validity).isBefore(now)) { if (!stillValid(now)) {
logger.debug("{} requesting fresh data for {}", getClass().getSimpleName(), thingUID); logger.debug("{} requesting fresh data for {}", getClass().getSimpleName(), thingUID);
List<NAObject> result = getFreshData(api); List<NAObject> result = getFreshData(api);
if (!result.isEmpty()) { if (!result.isEmpty()) {
@ -58,5 +58,9 @@ public abstract class CacheCapability<T extends RestManager> extends RestCapabil
return lastResult; return lastResult;
} }
protected boolean stillValid(Instant ts) {
return requestTS.plus(validity).isAfter(ts);
}
protected abstract List<NAObject> getFreshData(T api); protected abstract List<NAObject> getFreshData(T api);
} }

View File

@ -153,7 +153,7 @@ public class Capability {
public void expireData() { public void expireData() {
CommonInterface bridgeHandler = handler.getBridgeHandler(); CommonInterface bridgeHandler = handler.getBridgeHandler();
if (bridgeHandler != null && !handler.getCapabilities().containsKey(RefreshCapability.class)) { if (bridgeHandler != null && handler.getCapabilities().getRefresh().isEmpty()) {
bridgeHandler.expireData(); bridgeHandler.expireData();
} }
} }

View File

@ -49,4 +49,13 @@ public class CapabilityMap extends ConcurrentHashMap<Class<?>, Capability> {
cap.dispose(); 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();
}
} }

View File

@ -12,9 +12,9 @@
*/ */
package org.openhab.binding.netatmo.internal.handler.capability; package org.openhab.binding.netatmo.internal.handler.capability;
import java.time.Duration;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.netatmo.internal.handler.CommonInterface; import org.openhab.binding.netatmo.internal.handler.CommonInterface;
@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory;
*/ */
@NonNullByDefault @NonNullByDefault
public class ParentUpdateCapability extends Capability { 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 final Logger logger = LoggerFactory.getLogger(ParentUpdateCapability.class);
private Optional<ScheduledFuture<?>> job = Optional.empty(); private Optional<ScheduledFuture<?>> job = Optional.empty();
@ -40,13 +40,13 @@ public class ParentUpdateCapability extends Capability {
@Override @Override
public void initialize() { public void initialize() {
job = Optional.of(handler.getScheduler().schedule(() -> { job = handler.schedule(() -> {
logger.debug("Requesting parents data update for Thing {}", handler.getId()); logger.debug("Requesting parents data update for Thing '{}'", thingUID);
CommonInterface bridgeHandler = handler.getBridgeHandler(); CommonInterface bridgeHandler = handler.getBridgeHandler();
if (bridgeHandler != null) { if (bridgeHandler != null) {
bridgeHandler.expireData(); bridgeHandler.expireData();
} }
}, DEFAULT_DELAY_S, TimeUnit.SECONDS)); }, DEFAULT_DELAY);
} }
@Override @Override

View File

@ -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);
}
}

View File

@ -12,114 +12,107 @@
*/ */
package org.openhab.binding.netatmo.internal.handler.capability; package org.openhab.binding.netatmo.internal.handler.capability;
import static java.time.temporal.ChronoUnit.*;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault; 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.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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 * @author Gaël L'hopital - Initial contribution
* *
*/ */
@NonNullByDefault @NonNullByDefault
public class RefreshCapability extends Capability { public class RefreshCapability extends Capability {
private static final Duration DEFAULT_DELAY = Duration.of(20, SECONDS); protected static final Duration ASAP = Duration.ofSeconds(2);
private static final Duration PROBING_INTERVAL = Duration.of(120, SECONDS); protected static final Duration OFFLINE_DELAY = Duration.ofMinutes(15);
private static final Duration OFFLINE_INTERVAL = Duration.of(15, MINUTES); protected static final Duration PROBING_INTERVAL = Duration.ofMinutes(2);
private final Logger logger = LoggerFactory.getLogger(RefreshCapability.class); private final Logger logger = LoggerFactory.getLogger(RefreshCapability.class);
private Duration dataValidity; protected Duration dataValidity = PROBING_INTERVAL;
private Instant dataTimeStamp = Instant.now();
private Instant dataTimeStamp0 = Instant.MIN;
private Optional<ScheduledFuture<?>> refreshJob = Optional.empty(); 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); super(handler);
this.dataValidity = Duration.ofSeconds(Math.max(0, refreshInterval));
} }
@Override public void setInterval(Duration dataValidity) {
public void initialize() { if (dataValidity.isNegative() || dataValidity.isZero()) {
this.refreshConfigured = !probing(); throw new IllegalArgumentException("refreshInterval must be positive");
freeJobAndReschedule(2); }
this.dataValidity = dataValidity;
expireData();
} }
@Override @Override
public void dispose() { public void dispose() {
freeJobAndReschedule(0); stopJob();
super.dispose(); super.dispose();
} }
@Override @Override
public void expireData() { public void expireData() {
dataTimeStamp = Instant.now().minus(dataValidity); if (!expiring) {
freeJobAndReschedule(1); expiring = true;
} rescheduleJob(ASAP);
private Duration dataAge() {
return Duration.between(dataTimeStamp, Instant.now());
}
private boolean probing() {
return dataValidity.getSeconds() <= 0;
}
private void proceedWithUpdate() {
handler.proceedWithUpdate();
long delay;
if (!ThingStatus.ONLINE.equals(handler.getThing().getStatus())) {
logger.debug("{} is not ONLINE, special refresh interval is used", thingUID);
delay = OFFLINE_INTERVAL.toSeconds();
if (probing()) {
dataTimeStamp0 = Instant.MIN;
}
} else {
delay = refreshConfigured ? dataValidity.getSeconds()
: (probing() ? PROBING_INTERVAL : dataValidity.minus(dataAge()).plus(DEFAULT_DELAY)).toSeconds();
} }
delay = delay < 2 ? PROBING_INTERVAL.toSeconds() : delay;
logger.debug("{} refreshed, next one in {}s", thingUID, delay);
freeJobAndReschedule(delay);
} }
@Override @Override
protected void updateNAThing(NAThing newData) { protected void afterNewData(@Nullable NAObject newData) {
super.updateNAThing(newData); expiring = false;
newData.getLastSeen().map(ZonedDateTime::toInstant).ifPresent(tsInstant -> { super.afterNewData(newData);
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);
}
}
dataTimeStamp = tsInstant;
});
} }
private void freeJobAndReschedule(long delay) { protected Duration calcDelay() {
return dataValidity;
}
private void proceedWithUpdate() {
Duration delay;
handler.proceedWithUpdate();
if (!ThingStatus.ONLINE.equals(handler.getThing().getStatus())) {
delay = OFFLINE_DELAY;
logger.debug("Thing '{}' is not ONLINE, using special refresh interval", thingUID);
} else {
delay = calcDelay();
}
rescheduleJob(delay);
}
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();
}
}
logger.debug("'{}' next refresh in {}", thingUID, delay);
refreshJob = handler.schedule(this::proceedWithUpdate, delay);
}
private void stopJob() {
refreshJob.ifPresent(job -> job.cancel(true)); refreshJob.ifPresent(job -> job.cancel(true));
refreshJob = Optional.ofNullable(delay == 0 ? null refreshJob = Optional.empty();
: handler.getScheduler().schedule(() -> proceedWithUpdate(), delay, TimeUnit.SECONDS));
} }
} }

View File

@ -34,7 +34,7 @@ public class WeatherCapability extends CacheCapability<WeatherApi> {
private final Logger logger = LoggerFactory.getLogger(WeatherCapability.class); private final Logger logger = LoggerFactory.getLogger(WeatherCapability.class);
public WeatherCapability(CommonInterface handler) { public WeatherCapability(CommonInterface handler) {
super(handler, Duration.ofSeconds(2), WeatherApi.class); super(handler, Duration.ofSeconds(10), WeatherApi.class);
} }
@Override @Override

View File

@ -103,6 +103,19 @@
</parameter> </parameter>
</config-description> </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"> <config-description uri="netatmo:camera">
<parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true"> <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> <label>@text/config.equipmentId.label</label>
@ -140,7 +153,7 @@
<advanced>true</advanced> <advanced>true</advanced>
</parameter> </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> <label>@text/config.refreshInterval.label</label>
<description>@text/config.refreshInterval.description</description> <description>@text/config.refreshInterval.description</description>
<default>180</default> <default>180</default>

View File

@ -30,6 +30,10 @@ public class ModuleTypeTest {
// This did not exist prior to PR #16492 // This did not exist prior to PR #16492
return URI.create(BINDING_ID + ":camera"); 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 // This was previous method for calculating configuration URI
return URI.create(BINDING_ID + ":" return URI.create(BINDING_ID + ":"
+ (mt == ModuleType.ACCOUNT ? "api_bridge" + (mt == ModuleType.ACCOUNT ? "api_bridge"