]> git.basschouten.com Git - openhab-addons.git/commitdiff
[Netatmo] Binding rewrite without external dependencies - step 3 (#12357)
authorGaël L'hopital <gael@lhopital.org>
Fri, 6 May 2022 22:37:24 +0000 (00:37 +0200)
committerGitHub <noreply@github.com>
Fri, 6 May 2022 22:37:24 +0000 (00:37 +0200)
Signed-off-by: clinique <gael@lhopital.org>
Also-by: Laurent Garnier <lg.hc@free.fr>
175 files changed:
bundles/org.openhab.binding.netatmo/NOTICE
bundles/org.openhab.binding.netatmo/README.md
bundles/org.openhab.binding.netatmo/pom.xml
bundles/org.openhab.binding.netatmo/src/main/feature/feature.xml
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/APIUtils.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/ChannelTypeUtils.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NATherm1StateDescriptionProvider.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java
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/RefreshStrategy.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/WeatherUtils.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/action/RoomActions.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AircareApi.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiError.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/BodyResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EnergyApi.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/HomeApi.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ListBodyResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoException.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/RestManager.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/SecurityApi.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/AccessTokenResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Dashboard.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Device.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Event.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Home.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeData.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataModule.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataPerson.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataRoom.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusModule.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusPerson.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Location.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/LocationEx.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/MeasureBodyElem.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Module.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMain.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAModule.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAObject.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThing.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Person.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Ping.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Place.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Room.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Snapshot.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/ThermProgram.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/TimeTableItem.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Zone.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraAddress.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/BatteryHelper.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RadioHelper.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/ApiHandlerConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/BindingConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/MeasureConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NAThingConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBridgeConfiguration.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADeserializer.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMapDeserializer.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushType.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushTypeDeserializer.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/StrictEnumTypeAdapterFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryService.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/AbstractNetatmoThingHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/DeviceHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ModuleHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoBridgeHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDataListener.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDeviceHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoModuleHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AirCareCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/Capability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CapabilityMap.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/ChannelHelperCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/DeviceCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EventCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeSecurityThingCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/MeasureCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PersonCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PresenceCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RefreshCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RestCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RoomCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/WeatherCapability.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityExtChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryExtChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/ChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventPersonChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeEnergyChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeSecurityChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HumidityChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/LocationChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/MeasuresChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/NoiseChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PersonChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PresenceChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureExtChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RainChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RoomChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SetpointChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SignalChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureExtChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureOutChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/Therm1ChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampExtChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/WindChannelHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/homecoach/NAHealthyHomeCoachHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/presence/NAPresenceCameraHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoChannelTypeProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoDescriptionProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoThingTypeProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAMainHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule1Handler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule2Handler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule3Handler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule4Handler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/thermostat/NAPlugHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/thermostat/NATherm1Handler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/ChannelTypeUtils.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/WeatherUtils.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEvent.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEventPerson.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NetatmoServlet.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/WelcomeWebHookServlet.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeCameraHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomePersonHandler.java [deleted file]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/binding/binding.xml
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 [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_de.properties [deleted file]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_fr.properties [deleted file]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/aircare.xml [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/bridge.xml [deleted file]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/camera.xml [deleted file]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/channels.xml
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/common.xml [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/energy.xml [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/healthyhomecoach.xml [deleted file]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/security.xml [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/station.xml [deleted file]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/thermostat.xml [deleted file]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/weather.xml [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/welcomehome.xml [deleted file]
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/welcomeperson.xml [deleted file]
bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/MeasureTest.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/dto/NAObjectTest.java [new file with mode: 0644]
bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryServiceTest.java [deleted file]
bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/presence/NAPresenceCameraHandlerTest.java [deleted file]
bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandlerTest.java [deleted file]

index 33e1210cf574da90fee6c1b96e36b7c5b4203114..38d625e349232ff5ffcc71bd75e4692cdac12768 100644 (file)
@@ -11,40 +11,3 @@ https://www.eclipse.org/legal/epl-2.0/.
 == Source Code
 
 https://github.com/openhab/openhab-addons
-
-== Third-party Content
-
-commons-codec
-* License: Apache 2.0 License
-* Project; https://commons.apache.org/proper/commons-codec
-* Source:  https://commons.apache.org/proper/commons-codec
-
-gson-fire
-* License: Apache 2.0 License
-* Project: http://gsonfire.io
-* Source:  https://github.com/julman99/gson-fire
-
-json
-* License: JSON License
-* Project: https://www.json.org
-* Source:  https://github.com/douglascrockford/JSON-java
-
-okhttp
-* License: Apache 2.0 License
-* Project: https://square.github.io/okhttp
-* Source:  https://github.com/square/okhttp
-
-okio
-* License: Apache 2.0 License
-* Project: https://square.github.io/okio/2.x/okio/jvm/okio
-* Source:  https://github.com/square/okio
-
-oltu.oauth2
-* License: Apache 2.0 License
-* Project: https://oltu.apache.org
-* Source:  https://svn.apache.org/viewvc/oltu/trunk
-
-netatmo-swagger-decl
-* License: MIT License
-* Project: https://dev.netatmo.com
-* Source:  https://github.com/cbornet/netatmo-swagger-decl
index f210b919d4bef3880bea436ae3192965d06f3a42..9dccea907597a5a06aee050c27af779adc5edb08 100644 (file)
@@ -9,25 +9,12 @@ The Netatmo binding integrates the following Netatmo products:
 
 See https://www.netatmo.com/ for details on their product.
 
-Please note, recent Netatmo thermostats are not supported because they require the Energy API which is not yet implemented in the binding.
-Only older Netatmo thermostats compatible with the Thermostat API are supported.
-For the same reason, Netatmo valves are also not supported.
-
-
 ## Binding Configuration
 
-The binding has the following configuration options:
-
-| Parameter           | Name                 | Description                       |
-|---------------------|----------------------|-----------------------------------|
-| backgroundDiscovery | Background Discovery | If set to true, the device and its associated modules are updated in the discovery inbox at each API call run to refresh device data. Default is false. |
-
 Before setting up your 'Things', you will have to grant openHAB to access Netatmo API.
 Here is the procedure:
 
-### 1. Application Creation
-
-Create an application at https://dev.netatmo.com/apps/createanapp
+Create an application at https://dev.netatmo.com/dev/createapp
 
 The variables you will need to get to setup the binding are:
 
@@ -36,49 +23,73 @@ The variables you will need to get to setup the binding are:
 * `<USERNAME>` The username you use to connect to the Netatmo API (usually your mail address).
 * `<PASSWORD>` The password attached to the above username.
 
+The binding has the following configuration options:
 
-### 2. Bridge and Things Configuration
+| Parameter    | Type          | Description                                                                                |
+|--------------|---------------|--------------------------------------------------------------------------------------------|
+| features     | String        | The perimeter of functionalities given to the binding WEATHER, AIR_CARE, ENERGY, SECURITY  |
+| readFriends  | Boolean       | Enables or disables the discovery of guest weather stations.                               |
 
-Once you will get needed informations from the Netatmo API, you will be able to configure bridge and things.
 
-E.g.
+## Bridge Configuration
+
+You will have to create at first a bridge to handle communication with your Netatmo Application.
+
+The Account bridge has the following configuration options:
+
+-   **clientId:** Client ID provided for the application you created on http://dev.netatmo.com/createapp.
+-   **clientSecret:**  Client Secret provided for the application you created.
+-   **username:** Your Netatmo API username (email).
+-   **password:** Your Netatmo API password.
+-   **webHookUrl:** Protocol, public IP and port to access openHAB server from Internet.
+-   **reconnectInterval:** The reconnection interval to Netatmo API (in s).
+
+
+## List of supported things
+
+| 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                                                       |
+| 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                                                                        |
+| 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                                                                        |
+| 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                                                                        |
+| 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                                                                        |
+| valve           | Thing  | NRV            | A valve controlling a radiator.                                                                      | id                                                                        |
+
 
-```
-Bridge netatmo:netatmoapi:home [ clientId="<CLIENT_ID>", clientSecret="<CLIENT_SECRET>", username = "<USERNAME>", password = "<PASSWORD>", readStation=true|false, readHealthyHomeCoach=true|false, readThermostat=true|false, readWelcome=true|false, readPresence=true|false] {
-    Thing NAMain    inside  [ id="aa:aa:aa:aa:aa:aa" ]
-    Thing NAModule1 outside  [ id="yy:yy:yy:yy:yy:yy", parentId="aa:aa:aa:aa:aa:aa" ]
-    Thing NHC       homecoach  [ id="cc:cc:cc:cc:cc:cc", [refreshInterval=60000] ]
-    Thing NAPlug    plugtherm  [ id="bb:bb:bb:bb:bb:bb", [refreshInterval=60000] ]
-    Thing NATherm1  thermostat [ id="xx:xx:xx:xx:xx:xx", parentId="bb:bb:bb:bb:bb:bb" ]
-    Thing NAWelcomeHome home   [ id="58yyacaaexxxebca99x999x", refreshInterval=600000 ]
-    Thing NACamera camera [ id="cc:cc:cc:cc:cc:cc", parentId="58yyacaaexxxebca99x999x" ]
-    Thing NOC presenceOutdoorCamera [ id="dd:dd:dd:dd:dd:dd", parentId="58yyacaaexxxebca99x999x" ]
-    Thing NAWelcomePerson sysadmin [ id="aaaaaaaa-bbbb-cccc-eeee-zzzzzzzzzzzz", parentId="58yyacaaexxxebca99x999x" ]
-    ...
-}
-```
 
 
 ### Webhook
 
-For Welcome or Presence Camera, Netatmo servers can send push notifications to the Netatmo Binding by using a callback URL.
-The webhook URL is setup at bridge level using "Webhook Address" parameter.
+Netatmo servers can send push notifications to the Netatmo Binding by using a callback URL.
+The webhook URL is setup at binding level using "Webhook Address" parameter.
 You will define here public way to access your openHAB server:
 
 ```
-http(s)://xx.yy.zz.ww:8080
+http(s)://xx.yy.zz.ww:443
 ```
 
-Your Netatmo App will be configured automatically by the bridge to the endpoint :
+Your Netatmo App will be configured automatically by the bridge to the endpoint:
 
 ```
-http(s)://xx.yy.zz.ww:8080/netatmo/%id%/camera
+http(s)://xx.yy.zz.ww:443/netatmo
 ```
 
-where %id% is the id of your camera thing.
-
 Please be aware of Netatmo own limits regarding webhook usage that lead to a 24h ban-time when webhook does not answer 5 times.
 
+NB: Allowed ports for webhooks are 80, 88, 443 and 9443.
+
 
 ### Configure Things
 
@@ -87,7 +98,7 @@ First login with your user.
 Then some examples of the documentation contain the **real results** of your weather station.
 In order to try the examples, you need the `device_id` of your Netatmo station.
 You can find it in the configuration menu of the app (android or apple).
-Get the IDs of your devices (indoor, outdoor, rain gauge)
+Get the IDs of your devices (indoor, outdoor, rain gauge) 
 [here](https://dev.netatmo.com/resources/technical/reference/weather/getstationsdata).
 
 `main_device` is the ID of the "main device", the indoor sensor.
@@ -111,7 +122,7 @@ For example your serial number "h00bcdc" should end up as "02:00:00:00:bc:dc".
 
 ## Discovery
 
-If you did not manually create things in the *.things file, the Netatmo Binding is able to discover automatically all depending modules and devices from Netatmo website.
+If you did not manually create things in the *.things file, the Netatmo Binding is able to discover automatically all depending modules and devices.
 
 
 ## Channels
@@ -122,297 +133,173 @@ If you did not manually create things in the *.things file, the Netatmo Binding
 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.
 
-Example item for the **indoor module**:
-
-```
-Number Netatmo_Indoor_CO2 "CO2" <carbondioxide> { channel = "netatmo:NAMain:home:inside:Co2" }
-```
 
 **Supported channels for the main indoor module:**
 
-| Channel ID          | Item Type            | Description                                              |
-|---------------------|----------------------|----------------------------------------------------------|
-| Co2                 | Number:Dimensionless | Air quality                                              |
-| MinCo2              | Number:Dimensionless | Minimum CO2 on current day                               |
-| MinCo2ThisWeek      | Number:Dimensionless | Minimum CO2 this week                                    |
-| MinCo2ThisMonth     | Number:Dimensionless | Minimum CO2 this month                                   |
-| MaxCo2              | Number:Dimensionless | Maximum CO2 on current day                               |
-| MaxCo2ThisWeek      | Number:Dimensionless | Maximum CO2 this week                                    |
-| MaxCo2ThisMonth     | Number:Dimensionless | Maximum CO2 this month                                   |
-| DateMinCo2          | DateTime             | Date when minimum CO2 was reached on current day         |
-| DateMinCo2ThisWeek  | DateTime             | Date when minimum CO2 was reached this week              |
-| DateMinCo2ThisMonth | DateTime             | Date when minimum CO2 was reached this month             |
-| DateMaxCo2          | DateTime             | Date when maximum CO2 was reached on current day         |
-| DateMaxCo2ThisWeek  | DateTime             | Date when maximum CO2 was reached this week              |
-| DateMaxCo2ThisMonth | DateTime             | Date when maximum CO2 was reached this month             |
-| Temperature         | Number:Temperature   | Current temperature                                      |
-| TempTrend           | String               | Temperature evolution trend (up, down, stable)           |
-| Noise               | Number:Dimensionless | Current noise level                                      |
-| MinNoise            | Number:Dimensionless | Minimum noise on current day                             |
-| MinNoiseThisWeek    | Number:Dimensionless | Minimum noise this week                                  |
-| MinNoiseThisMonth   | Number:Dimensionless | Minimum noise this month                                 |
-| MaxNoise            | Number:Dimensionless | Maximum noise on current day                             |
-| MaxNoiseThisWeek    | Number:Dimensionless | Maximum noise this week                                  |
-| MaxNoiseThisMonth   | Number:Dimensionless | Maximum noise this month                                 |
-| DateMinNoise        | DateTime             | Date when minimum noise was reached on current day       |
-| DateMinNoiseThisWeek| DateTime             | Date when minimum noise was reached this week            |
-| DateMinNoiseThisMonth| DateTime            | Date when minimum noise was reached this month           |
-| DateMaxNoise        | DateTime             | Date when maximum noise was reached on current day       |
-| DateMaxNoiseThisWeek| DateTime             | Date when maximum noise was reached this week            |
-| DateMaxNoiseThisMonth| DateTime            | Date when maximum noise was reached this month           |
-| Pressure            | Number:Pressure      | Current pressure                                         |
-| MinPressure         | Number:Pressure      | Minimum pressure on current day                          |
-| MinPressureThisWeek | Number:Pressure      | Minimum pressure this week                               |
-| MinPressureThisMonth| Number:Pressure      | Minimum pressure this month                              |
-| MaxPressure         | Number:Pressure      | Maximum pressure on current day                          |
-| MaxPressureThisWeek | Number:Pressure      | Maximum pressure this week                               |
-| MaxPressureThisMonth| Number:Pressure      | Maximum pressure this month                              |
-| DateMinPressure     | DateTime             | Date when minimum pressure was reached on current day    |
-| DateMinPressureThisWeek | DateTime         | Date when minimum pressure was reached this week         |
-| DateMinPressureThisMonth| DateTime         | Date when minimum pressure was reached this month        |
-| DateMaxPressure     | DateTime             | Date when maximum pressure was reached on current day    |
-| DateMaxPressureThisWeek | DateTime         | Date when maximum pressure was reached this week         |
-| DateMaxPressureThisMonth| DateTime         | Date when maximum pressure was reached this month        |
-| PressTrend          | String               | Pressure evolution trend for last 12h (up, down, stable) |
-| AbsolutePressure    | Number:Pressure      | Absolute pressure                                        |
-| Humidity            | Number:Dimensionless | Current humidity                                         |
-| MinHumidity         | Number:Dimensionless | Minimum humidity on current day                          |
-| MinHumidityThisWeek | Number:Dimensionless | Minimum humidity this week                               |
-| MinHumidityThisMonth| Number:Dimensionless | Minimum humidity this month                              |
-| MaxHumidity         | Number:Dimensionless | Maximum humidity on current day                          |
-| MaxHumidityThisWeek | Number:Dimensionless | Maximum humidity this week                               |
-| MaxHumidityThisMonth| Number:Dimensionless | Maximum humidity this month                              |
-| DateMinHumidity     | DateTime             | Date when minimum humidity was reached on current day    |
-| DateMinHumidityThisWeek | DateTime         | Date when minimum humidity was reached this week         |
-| DateMinHumidityThisMonth| DateTime         | Date when minimum humidity was reached this month        |
-| DateMaxHumidity     | DateTime             | Date when maximum humidity was reached on current day    |
-| DateMaxHumidityThisWeek | DateTime         | Date when maximum humidity was reached this week         |
-| DateMaxHumidityThisMonth| DateTime         | Date when maximum humidity was reached this month        |
-| Humidex             | Number               | Computed Humidex index                                   |
-| HeatIndex           | Number:Temperature   | Computed Heat Index                                      |
-| Dewpoint            | Number:Temperature   | Computed dewpoint temperature                            |
-| DewpointDepression  | Number:Temperature   | Computed dewpoint depression                             |
-| MinTemp             | Number:Temperature   | Minimum temperature on current day                       |
-| MinTempThisWeek     | Number:Temperature   | Minimum temperature this week                            |
-| MinTempThisMonth    | Number:Temperature   | Minimum temperature this month                           |
-| MaxTemp             | Number:Temperature   | Maximum temperature on current day                       |
-| MaxTempThisWeek     | Number:Temperature   | Maximum temperature this week                            |
-| MaxTempThisMonth    | Number:Temperature   | Maximum temperature this month                           |
-| DateMinTemp         | DateTime             | Date when minimum temperature was reached on current day |
-| DateMinTempThisWeek | DateTime             | Date when minimum temperature was reached this week      |
-| DateMinTempThisMonth| DateTime             | Date when minimum temperature was reached this month     |
-| DateMaxTemp         | DateTime             | Date when maximum temperature was reached on current day |
-| DateMaxTempThisWeek | DateTime             | Date when maximum temperature was reached this week      |
-| DateMaxTempThisMonth| DateTime             | Date when maximum temperature was reached this month     |
-| DateMinTemp         | DateTime             | Date when minimum temperature was reached on current day |
-| DateMaxTemp         | DateTime             | Date when maximum temperature was reached on current day |
-| TimeStamp           | DateTime             | Timestamp when data was measured                         |
-| LastStatusStore     | DateTime             | Last status store                                        |
-| WifiStatus          | Number               | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
-| Location            | Location             | Location of the device                                   |
+| Channel Group       | Channel Id           | Item Type            | Description                                      |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| pressure            | value                | Number:Pressure      | Current pressure                                 |
+| pressure            | absolute             | Number:Pressure      | Pressure at sea level                            |
+| pressure            | trend                | String               | Pressure evolution trend over time               |
+| noise               | value                | Number:Dimensionless | Current noise level                              |
+| humidity            | value                | Number:Dimensionless | Current humidity                                 |
+| humidity            | humidex              | Number               | Computed Humidex index                           |
+| humidity            | humidex-scale        | Number               | Humidex index appreciation                       |
+| temperature         | value                | Number:Temperature   | Current temperature                              |
+| temperature         | min-today            | Number:Temperature   | Minimum temperature on current day               |
+| temperature         | max-today            | Number:Temperature   | Maximum temperature on current day               |
+| temperature         | min-time             | DateTime             | Moment of today's minimum temperature            |
+| temperature         | max-time             | DateTime             | Moment of today's maximum temperature            |
+| temperature         | trend                | String               | Temperature evolution trend over time            |
+| temperature         | heat-index           | Number:Temperature   | Computed Heat Index                              |
+| temperature         | dewpoint             | Number:Temperature   | Computed dewpoint temperature                    |
+| temperature         | dewpoint-depression  | Number:Temperature   | Computed dewpoint depression                     |
+| airquality          | co2                  | Number:Dimensionless | CO2 level in ppm                                 |
+| location            | value                | Location             | Location of the device                           |
+| timestamp           | last-seen            | DateTime             | Last time the module reported its presence       |
+| timestamp           | measures             | DateTime             | Moment of the last measures update               |
+| signal              | strength             | Number               | Signal strength (0 for no signal, 1 for weak...) |
+| signal              | value                | Number:Power         | Signal strength in dBm                           |
 
 All these channels are read only.
 
 
 ### Weather Station Outdoor module
 
-Example item for the **outdoor module**
-
-```
-Number Netatmo_Outdoor_Temperature "Temperature" { channel = "netatmo:NAModule1:home:outside:Temperature" }
-```
-
 **Supported channels for the outdoor module:**
 
-| Channel ID          | Item Type            | Description                                              |
-|---------------------|----------------------|----------------------------------------------------------|
-| Temperature         | Number:Temperature   | Current temperature                                      |
-| TempTrend           | String               | Temperature evolution trend (up, down, stable)           |
-| Humidity            | Number:Dimensionless | Current humidity                                         |
-| MinHumidity         | Number:Dimensionless | Minimum humidity on current day                          |
-| MinHumidityThisWeek | Number:Dimensionless | Minimum humidity this week                               |
-| MinHumidityThisMonth| Number:Dimensionless | Minimum humidity this month                              |
-| MaxHumidity         | Number:Dimensionless | Maximum humidity on current day                          |
-| MaxHumidityThisWeek | Number:Dimensionless | Maximum humidity this week                               |
-| MaxHumidityThisMonth| Number:Dimensionless | Maximum humidity this month                              |
-| DateMinHumidity     | DateTime             | Date when minimum humidity was reached on current day    |
-| DateMinHumidityThisWeek | DateTime         | Date when minimum humidity was reached this week         |
-| DateMinHumidityThisMonth| DateTime         | Date when minimum humidity was reached this month        |
-| DateMaxHumidity     | DateTime             | Date when maximum humidity was reached on current day    |
-| DateMaxHumidityThisWeek | DateTime         | Date when maximum humidity was reached this week         |
-| DateMaxHumidityThisMonth| DateTime         | Date when maximum humidity was reached this month        |
-| Humidex             | Number               | Computed Humidex index                                   |
-| HeatIndex           | Number:Temperature   | Computed Heat Index                                      |
-| Dewpoint            | Number:Temperature   | Computed dewpoint temperature                            |
-| DewpointDepression  | Number:Temperature   | Computed dewpoint depression                             |
-| MinTemp             | Number:Temperature   | Minimum temperature on current day                       |
-| MinTempThisWeek     | Number:Temperature   | Minimum temperature this week                            |
-| MinTempThisMonth    | Number:Temperature   | Minimum temperature this month                           |
-| MaxTemp             | Number:Temperature   | Maximum temperature on current day                       |
-| MaxTempThisWeek     | Number:Temperature   | Maximum temperature this week                            |
-| MaxTempThisMonth    | Number:Temperature   | Maximum temperature this month                           |
-| DateMinTemp         | DateTime             | Date when minimum temperature was reached on current day |
-| DateMinTempThisWeek | DateTime             | Date when minimum temperature was reached this week      |
-| DateMinTempThisMonth| DateTime             | Date when minimum temperature was reached this month     |
-| DateMaxTemp         | DateTime             | Date when maximum temperature was reached on current day |
-| DateMaxTempThisWeek | DateTime             | Date when maximum temperature was reached this week      |
-| DateMaxTempThisMonth| DateTime             | Date when maximum temperature was reached this month     |
-| TimeStamp           | DateTime             | Timestamp when data was measured                         |
-| LastMessage         | DateTime             | Last message emitted by the module                       |
-| LowBattery          | Switch               | Low battery                                              |
-| BatteryVP           | Number               | Battery level                                            |
-| RfStatus            | Number               | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
+| Channel Group       | Channel Id           | Item Type            | Description                                      |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| humidity            | value                | Number:Dimensionless | Current humidity                                 |
+| humidity            | humidex              | Number               | Computed Humidex index                           |
+| humidity            | humidex-scale        | Number               | Humidex index appreciation                       |
+| temperature         | value                | Number:Temperature   | Current temperature                              |
+| temperature         | min-today            | Number:Temperature   | Minimum temperature on current day               |
+| temperature         | max-today            | Number:Temperature   | Maximum temperature on current day               |
+| temperature         | min-time             | DateTime             | Moment of today's minimum temperature            |
+| temperature         | max-time             | DateTime             | Moment of today's maximum temperature            |
+| temperature         | trend                | String               | Temperature evolution trend over time            |
+| temperature         | heat-index           | Number:Temperature   | Computed Heat Index                              |
+| temperature         | dewpoint             | Number:Temperature   | Computed dewpoint temperature                    |
+| temperature         | dewpoint-depression  | Number:Temperature   | Computed dewpoint depression                     |
+| timestamp           | last-seen            | DateTime             | Last time the module reported its presence       |
+| timestamp           | measures             | DateTime             | Moment of the last measures update               |
+| signal              | strength             | Number               | Signal strength (0 for no signal, 1 for weak...) |
+| signal              | value                | Number:Power         | Signal strength in dBm                           |
+| battery             | value                | Number               | Battery level                                    |
+| battery             | low-battery          | Switch               | Low battery                                      |
 
 All these channels are read only.
 
 
 ### Weather Station Additional Indoor module
 
-Example item for the **indoor module**
-
-```
-Number Netatmo_Indoor2_Temperature "Temperature" { channel = "netatmo:NAModule4:home:insidesupp:Temperature" }
-```
 
 **Supported channels for the additional indoor module:**
 
-| Channel ID          | Item Type            | Description                                              |
-|---------------------|----------------------|----------------------------------------------------------|
-| Co2                 | Number:Dimensionless | Air quality                                              |
-| MinCo2              | Number:Dimensionless | Minimum CO2 on current day                               |
-| MinCo2ThisWeek      | Number:Dimensionless | Minimum CO2 this week                                    |
-| MinCo2ThisMonth     | Number:Dimensionless | Minimum CO2 this month                                   |
-| MaxCo2              | Number:Dimensionless | Maximum CO2 on current day                               |
-| MaxCo2ThisWeek      | Number:Dimensionless | Maximum CO2 this week                                    |
-| MaxCo2ThisMonth     | Number:Dimensionless | Maximum CO2 this month                                   |
-| DateMinCo2          | DateTime             | Date when minimum CO2 was reached on current day         |
-| DateMinCo2ThisWeek  | DateTime             | Date when minimum CO2 was reached this week              |
-| DateMinCo2ThisMonth | DateTime             | Date when minimum CO2 was reached this month             |
-| DateMaxCo2          | DateTime             | Date when maximum CO2 was reached on current day         |
-| DateMaxCo2ThisWeek  | DateTime             | Date when maximum CO2 was reached this week              |
-| DateMaxCo2ThisMonth | DateTime             | Date when maximum CO2 was reached this month             |
-| Temperature         | Number:Temperature   | Current temperature                                      |
-| TempTrend           | String               | Temperature evolution trend (up, down, stable)           |
-| Humidity            | Number:Dimensionless | Current humidity                                         |
-| MinHumidity         | Number:Dimensionless | Minimum humidity on current day                          |
-| MinHumidityThisWeek | Number:Dimensionless | Minimum humidity this week                               |
-| MinHumidityThisMonth| Number:Dimensionless | Minimum humidity this month                              |
-| MaxHumidity         | Number:Dimensionless | Maximum humidity on current day                          |
-| MaxHumidityThisWeek | Number:Dimensionless | Maximum humidity this week                               |
-| MaxHumidityThisMonth| Number:Dimensionless | Maximum humidity this month                              |
-| DateMinHumidity     | DateTime             | Date when minimum humidity was reached on current day    |
-| DateMinHumidityThisWeek | DateTime         | Date when minimum humidity was reached this week         |
-| DateMinHumidityThisMonth| DateTime         | Date when minimum humidity was reached this month        |
-| DateMaxHumidity     | DateTime             | Date when maximum humidity was reached on current day    |
-| DateMaxHumidityThisWeek | DateTime         | Date when maximum humidity was reached this week         |
-| DateMaxHumidityThisMonth| DateTime         | Date when maximum humidity was reached this month        |
-| Humidex             | Number               | Computed Humidex index                                   |
-| HeatIndex           | Number:Temperature   | Computed Heat Index                                      |
-| Dewpoint            | Number:Temperature   | Computed dewpoint temperature                            |
-| DewpointDepression  | Number:Temperature   | Computed dewpoint depression                             |
-| MinTemp             | Number:Temperature   | Minimum temperature on current day                       |
-| MinTempThisWeek     | Number:Temperature   | Minimum temperature this week                            |
-| MinTempThisMonth    | Number:Temperature   | Minimum temperature this month                           |
-| MaxTemp             | Number:Temperature   | Maximum temperature on current day                       |
-| MaxTempThisWeek     | Number:Temperature   | Maximum temperature this week                            |
-| MaxTempThisMonth    | Number:Temperature   | Maximum temperature this month                           |
-| DateMinTemp         | DateTime             | Date when minimum temperature was reached on current day |
-| DateMinTempThisWeek | DateTime             | Date when minimum temperature was reached this week      |
-| DateMinTempThisMonth| DateTime             | Date when minimum temperature was reached this month     |
-| DateMaxTemp         | DateTime             | Date when maximum temperature was reached on current day |
-| DateMaxTempThisWeek | DateTime             | Date when maximum temperature was reached this week      |
-| DateMaxTempThisMonth| DateTime             | Date when maximum temperature was reached this month     |
-| TimeStamp           | DateTime             | Timestamp when data was measured                         |
-| LastMessage         | DateTime             | Last message emitted by the module                       |
-| LowBattery          | Switch               | Low battery                                              |
-| BatteryVP           | Number               | Battery level                                            |
-| RfStatus            | Number               | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
+| Channel Group       | Channel Id           | Item Type            | Description                                      |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| humidity            | value                | Number:Dimensionless | Current humidity                                 |
+| humidity            | humidex              | Number               | Computed Humidex index                           |
+| humidity            | humidex-scale        | Number               | Humidex index appreciation                       |
+| temperature         | value                | Number:Temperature   | Current temperature                              |
+| temperature         | min-today            | Number:Temperature   | Minimum temperature on current day               |
+| temperature         | max-today            | Number:Temperature   | Maximum temperature on current day               |
+| temperature         | min-time             | DateTime             | Moment of today's minimum temperature            |
+| temperature         | max-time             | DateTime             | Moment of today's maximum temperature            |
+| temperature         | trend                | String               | Temperature evolution trend over time            |
+| temperature         | heat-index           | Number:Temperature   | Computed Heat Index                              |
+| temperature         | dewpoint             | Number:Temperature   | Computed dewpoint temperature                    |
+| temperature         | dewpoint-depression  | Number:Temperature   | Computed dewpoint depression                     |
+| airquality          | co2                  | Number:Dimensionless | Air quality                                      |
+| timestamp           | last-seen            | DateTime             | Last time the module reported its presence       |
+| timestamp           | measures             | DateTime             | Moment of the last measures update               |
+| signal              | strength             | Number               | Signal strength (0 for no signal, 1 for weak...) |
+| signal              | value                | Number:Power         | Signal strength in dBm                           |
+| battery             | value                | Number               | Battery level                                    |
+| battery             | low-battery          | Switch               | Low battery                                      |
 
 All these channels are read only.
 
 
 ### Rain Gauge
 
-Example item for the **rain gauge**
-
-```
-Number Netatmo_Rain_Current "Rain [%.1f mm]" { channel = "netatmo:NAModule3:home:rain:Rain" }
-```
 
 **Supported channels for the rain guage:**
 
-| Channel ID          | Item Type     | Description                                              |
-|---------------------|---------------|----------------------------------------------------------|
-| Rain                | Number:Length | Quantity of water                                        |
-| SumRain1            | Number:Length | Quantity of water on last hour                           |
-| SumRain24           | Number:Length | Quantity of water on last day                            |
-| SumRainThisWeek     | Number:Length | Quantity of water this week                              |
-| SumRainThisMonth    | Number:Length | Quantity of water this month                             |
-| TimeStamp           | DateTime      | Timestamp when data was measured                         |
-| LastMessage         | DateTime      | Last message emitted by the module                       |
-| LowBattery          | Switch        | Low battery                                              |
-| BatteryVP           | Number        | Battery level                                            |
-| RfStatus            | Number        | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
+| Channel Group       | Channel Id           | Item Type            | Description                                      |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| rain                | value                | Number:Speed         | Current precipitation intensity                  |
+| rain                | sum-1                | Number:Length        | Quantity of water over last hour                 |
+| rain                | sum-24               | Number:Length        | Quantity of water during the current day         |
+| timestamp           | last-seen            | DateTime             | Last time the module reported its presence       |
+| timestamp           | measures             | DateTime             | Moment of the last measures update               |
+| signal              | strength             | Number               | Signal strength (0 for no signal, 1 for weak...) |
+| signal              | value                | Number:Power         | Signal strength in dBm                           |
+| battery             | value                | Number               | Battery level                                    |
+| battery             | low-battery          | Switch               | Low battery                                      |
 
 All these channels are read only.
 
 
 ### Weather Station Wind module
 
-Example item for the **wind module**:
-
-```
-Number Netatmo_Wind_Strength "Wind Strength [%.0f KPH]" { channel = "netatmo:NAModule2:home:wind:WindStrength" }
-```
 
 **Supported channels for the wind module:**
 
-| Channel ID          | Item Type    | Description                                              |
-|---------------------|--------------|----------------------------------------------------------|
-| WindAngle           | Number:Angle | Current 5 minutes average wind direction                 |
-| WindStrength        | Number:Speed | Current 5 minutes average wind speed                     |
-| GustAngle           | Number:Angle | Direction of the last 5 minutes highest gust wind        |
-| GustStrength        | Number:Speed | Speed of the last 5 minutes highest gust wind            |
-| TimeStamp           | DateTime     | Timestamp when data was measured                         |
-| LastMessage         | DateTime     | Last message emitted by the module                       |
-| LowBattery          | Switch       | Low battery                                              |
-| BatteryVP           | Number       | Battery level                                            |
-| RfStatus            | Number       | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
-| MaxWindStrength     | Number:Speed | Maximum wind strength recorded                           |
-| DateMaxWindStrength | DateTime     | Timestamp when MaxWindStrength was recorded              |
+| Channel Group       | Channel Id           | Item Type            | Description                                      |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| wind                | angle                | Number:Angle         | Current 5 minutes average wind direction         |
+| wind                | strength             | Number:Speed         | Current 5 minutes average wind speed             |
+| wind                | max-strength         | Number:Speed         | Maximum wind strength recorded                   |
+| wind                | max-strength-date    | DateTime             | Moment when MaxWindStrength was recorded         |
+| wind                | gust-angle           | Number:Angle         | Direction of the last 5 minutes highest gust     |
+| wind                | gust-strength        | Number:Speed         | Speed of the last 5 minutes highest gust wind    |
+| timestamp           | last-seen            | DateTime             | Last time the module reported its presence       |
+| timestamp           | measures             | DateTime             | Moment of the last measures update               |
+| signal              | strength             | Number               | Signal strength (0 for no signal, 1 for weak...) |
+| signal              | value                | Number:Power         | Signal strength in dBm                           |
+| battery             | value                | Number               | Battery level                                    |
+| battery             | low-battery          | Switch               | Low battery                                      |
 
 All these channels are read only.
 
 
 ### Healthy Home Coach Device
 
-Example item for the **Healthy Home Coach**:
-
-```
-String Netatmo_LivingRoom_HomeCoach_HealthIndex "Climate" { channel = "netatmo:NHC:home:livingroom:HealthIndex" }
-```
 
 **Supported channels for the healthy home coach device:**
 
-| Channel ID          | Item Type            | Description                                              |
-|---------------------|----------------------|----------------------------------------------------------|
-| HealthIndex         | String               | Health index (healthy, fine, fair, poor, unhealthy)      |
-| Co2                 | Number:Dimensionless | Air quality                                              |
-| Temperature         | Number:Temperature   | Current temperature                                      |
-| TempTrend           | String               | Temperature evolution trend (up, down, stable)           |
-| Noise               | Number:Dimensionless | Current noise level                                      |
-| Pressure            | Number:Pressure      | Current pressure                                         |
-| PressTrend          | String               | Pressure evolution trend for last 12h (up, down, stable) |
-| AbsolutePressure    | Number:Pressure      | Absolute pressure                                        |
-| Humidity            | Number:Dimensionless | Current humidity                                         |
-| MinTemp             | Number:Temperature   | Minimum temperature on current day                       |
-| MaxTemp             | Number:Temperature   | Maximum temperature on current day                       |
-| DateMinTemp         | DateTime             | Date when minimum temperature was reached on current day |
-| DateMaxTemp         | DateTime             | Date when maximum temperature was reached on current day |
-| TimeStamp           | DateTime             | Timestamp when data was measured                         |
-| LastStatusStore     | DateTime             | Last status store                                        |
-| WifiStatus          | Number               | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
-| Location            | Location             | Location of the device                                   |
+| Channel Group       | Channel Id           | Item Type            | Description                                      |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| noise               | value                | Number:Dimensionless | Current noise level                              |
+| humidity            | value                | Number:Dimensionless | Current humidity                                 |
+| humidity            | humidex              | Number               | Computed Humidex index                           |
+| humidity            | humidex-scale        | Number               | Humidex index appreciation                       |
+| pressure            | value                | Number:Pressure      | Current pressure                                 |
+| pressure            | absolute             | Number:Pressure      | Pressure at sea level                            |
+| temperature         | value                | Number:Temperature   | Current temperature                              |
+| temperature         | min-today            | Number:Temperature   | Minimum temperature on current day               |
+| temperature         | max-today            | Number:Temperature   | Maximum temperature on current day               |
+| temperature         | min-time             | DateTime             | Moment of today's minimum temperature            |
+| temperature         | max-time             | DateTime             | Moment of today's maximum temperature            |
+| temperature         | heat-index           | Number:Temperature   | Computed Heat Index                              |
+| temperature         | dewpoint             | Number:Temperature   | Computed dewpoint temperature                    |
+| temperature         | dewpoint-depression  | Number:Temperature   | Computed dewpoint depression                     |
+| airquality          | health-index         | Number               | Health index (*)                                 |
+| airquality          | co2                  | Number:Dimensionless | Air quality                                      |
+| timestamp           | last-seen            | DateTime             | Last time the module reported its presence       |
+| timestamp           | measures             | DateTime             | Moment of the last measures update               |
+| signal              | strength             | Number               | Signal strength (0 for no signal, 1 for weak...) |
+| signal              | value                | Number:Power         | Signal strength in dBm                           |
+
+(*) Health index values : 
+
+- 0 : healthy 
+- 1 : fine 
+- 2 : fair 
+- 3 : poor
+- 4 : unhealthy
 
 All these channels are read only.
 
@@ -421,38 +308,71 @@ All these channels are read only.
 
 **Supported channels for the thermostat relay device:**
 
-| Channel ID          | Item Type | Description                                              |
-|---------------------|-----------|----------------------------------------------------------|
-| ConnectedBoiler     | Switch    | Plug connected boiler                                    |
-| LastPlugSeen        | DateTime  | Last plug seen                                           |
-| LastBilan           | DateTime  | Month of the last available thermostat bilan             |
-| LastStatusStore     | DateTime  | Last status store                                        |
-| WifiStatus          | Number    | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
-| Location            | Location  | Location of the device                                   |
+| Channel Group       | Channel Id         | Item Type            | Description                                      |
+|---------------------|--------------------|----------------------|--------------------------------------------------|
+| signal              | strength           | Number               | Signal strength (0 for no signal, 1 for weak...) |
+| signal              | value              | Number:Power         | Signal strength in dBm                           |
 
 All these channels are read only.
 
 
+### Thermostat Plug
+
+**Supported channels for the thermostat plug device:**
+
+| Channel Group       | Channel Id         | Item Type            | Description                                      |
+|---------------------|--------------------|----------------------|--------------------------------------------------|
+| signal              | strength           | Number               | Signal strength (0 for no signal, 1 for weak...) |
+| signal              | value              | Number:Power         | Signal strength in dBm                           |
+
+All these channels are read only.
+
+
+### Room
+
+**Supported channels for the Room thing:**
+
+| Channel Group    | Channel Id            | Item Type            | Description                                             |
+|------------------|-----------------------|----------------------|---------------------------------------------------------|
+| room-temperature | value                 | Number:Temperature   | Current temperature in the room                         |
+| room-properties  | window-open           | Contact              | Windows of the room are opened                          |
+| room-properties  | anticipating          | Switch               | Anticipates next scheduled setpoint                     |
+| room-properties  | heating-power-request | Number:Dimensionless | Percentage of heating power                             |
+| setpoint         | value                 | Number:Temperature   | Thermostat temperature setpoint                         |
+| setpoint         | mode                  | String               | Chosen thermostat mode (home, frost guard, manual, max) |
+| setpoint         | start                 | DateTime             | Start time of the currently applied setpoint            |
+| setpoint         | end                   | DateTime             | End time of the currently applied setpoint              |
+
+All these channels except setpoint and setpoint-mode are read only.
+
+
 ### Thermostat Module
 
 **Supported channels for the thermostat module:**
 
-| Channel ID          | Item Type          | Description                                                |
-|---------------------|--------------------|------------------------------------------------------------|
-| Temperature         | Number:Temperature | Current temperature                                        |
-| Sp_Temperature      | Number:Temperature | Thermostat temperature setpoint                            |
-| SetpointMode        | String             | Chosen setpoint_mode (program, away, hg, manual, off, max) |
-| Planning            | String             | Id of the currently active planning when mode = program    |
-| ThermRelayCmd       | Switch             | Indicates whether the furnace is heating or not            |
-| ThermOrientation    | Number             | Physical orientation of the thermostat module              |
-| TimeStamp           | DateTime           | Timestamp when data was measured                           |
-| SetpointEndTime     | DateTime           | Thermostat goes back to schedule after that timestamp      |
-| LastMessage         | DateTime           | Last message emitted by the module                         |
-| LowBattery          | Switch             | Low battery                                                |
-| BatteryVP           | Number             | Battery level                                              |
-| RfStatus            | Number             | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
+| Channel Group       | Channel Id         | Item Type            | Description                                      |
+|---------------------|--------------------|----------------------|--------------------------------------------------|
+| th-properties       | relay-status       | Contact              | Indicates if the boiler is currently heating     |
+| signal              | strength           | Number               | Signal strength (0 for no signal, 1 for weak...) |
+| signal              | value              | Number:Power         | Signal strength in dBm                           |
+| battery             | value              | Number               | Battery level                                    |
+| battery             | low-battery        | Switch               | Low battery                                      |
+| battery             | status             | String               | Description of the battery status (*)            |
+
+(*) Can be UNDEF on some modules
+
 
-All these channels except Sp_Temperature, SetpointMode and Planning are read only.
+### Valve Module
+
+**Supported channels for the Valve module:**
+
+| Channel Group       | Channel Id         | Item Type            | Description                                      |
+|---------------------|--------------------|----------------------|--------------------------------------------------|
+| signal              | strength           | Number               | Signal strength (0 for no signal, 1 for weak...) |
+| signal              | value              | Number:Power         | Signal strength in dBm                           |
+| battery             | value              | Number               | Battery level                                    |
+| battery             | low-battery        | Switch               | Low battery                                      |
+| battery             | status             | String               | Description of the battery status (*)            |
 
 
 ### Welcome Home
@@ -461,54 +381,44 @@ All these channels are read only.
 
 **Supported channels for the Home thing:**
 
-| Channel ID               | Item Type | Description                                              |
-|--------------------------|-----------|----------------------------------------------------------|
-| welcomeHomeCity          | String    | City of the home                                         |
-| welcomeHomeCountry       | String    | Country of the home                                      |
-| welcomeHomeTimezone      | String    | Timezone of the home                                     |
-| welcomeHomePersonCount   | Number    | Total number of Persons that are at home                 |
-| welcomeHomeUnknownCount  | Number    | Count how many Unknown Persons are at home               |
-| welcomeEventType         | String    | Type of event                                            |
-| welcomeEventTime         | DateTime  | Time of occurrence of event                               |
-| welcomeEventCameraId     | String    | Camera that detected the event                           |
-| welcomeEventPersonId     | String    | Id of the person the event is about (if any)             |
-| welcomeEventSnapshot     | Image     | picture of the last event, if it applies                 |
-| welcomeEventSnapshotURL  | String    | if the last event (depending upon event type) in the home lead a snapshot picture, the picture URL will be available here |
-| welcomeEventVideoURL     | String    | if the last event (depending upon event type) in the home lead a snapshot picture, the corresponding video URL will be available here |
-| welcomeEventVideoStatus  | String    | Status of the video (recording, deleted or available)    |
-| welcomeEventIsArrival    | Switch    | If person was considered "away" before being seen during this event |
-| welcomeEventMessage      | String    | Message sent by Netatmo corresponding to given event     |
-| welcomeEventSubType      | String    | Sub-type of SD and Alim events                           |
+| Channel Group       | Channel Id             | Item Type        | Description                                      |
+|---------------------|------------------------|------------------|--------------------------------------------------|
+| security            | person-count           | Number           | Total number of persons that are at home         |
+| security            | unknown-person-count   | Number           | Total number of unknown persons that are at home |
+| security            | unknown-person-picture | Image            | Snapshot of unknown person that is at home       |
+
+All these channels are read only.
+
 
 **Supported trigger channels for the Home thing:**
 
-| Channel Type ID  | Options                | Description                                           |
-|------------------|------------------------|-------------------------------------------------------|
+| Channel Type ID  | Options                | Description                                                                                                                                                                      |
+|------------------|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | cameraEvent      |                        | A camera event is triggered with a short delay but without requiring a webhook. The information of the event can get retrieved from the other "welcomeEvent" home thing channels |
-|                  | HUMAN                  | Triggered when a human (or person) was detected       |
-|                  | ANIMAL                 | Triggered when an animal was detected                 |
-|                  | MOVEMENT               | Triggered when an unspecified movement was detected   |
-|                  | VEHICLE                | Triggered when a vehicle was detected                 |
-| welcomeHomeEvent |                        | A welcome home event is triggered directly via a configured webhook |
-|                  | PERSON                 | Triggered when a concrete person was detected         |
-|                  | PERSON_AWAY            | Triggered when a concrete person leaves               |
-|                  | MOVEMENT               | Triggered when a movement was detected                |
-|                  | CONNECTION             | Triggered when a camera connection gets created       |
-|                  | DISCONNECTION          | Triggered when a camera connection got lost           |
-|                  | ON                     | Triggered when camera monitoring is switched on       |
-|                  | OFF                    | Triggered when camera monitoring is switched off      |
-|                  | BOOT                   | Triggered when a camera is booting                    |
-|                  | SD                     | Triggered when a camera SD card status was changed    |
-|                  | ALIM                   | Triggered when a power supply status was changed      |
-|                  | NEW_MODULE             | Triggered when a new module was discovered            |
-|                  | MODULE_CONNECT         | Triggered when a module gets connected                |
-|                  | MODULE_DISCONNECT      | Triggered when a module gets disconnected             |
-|                  | MODULE_LOW_BATTERY     | Triggered when the battery of a module gets low       |
-|                  | MODULE_END_UPDATE      | Triggered when a firmware update of a module is done  |
-|                  | TAG_BIG_MOVE           | Triggered when a big movement of a tag was detected   |
-|                  | TAG_SMALL_MOVE         | Triggered when a small movement of a tag was detected |
-|                  | TAG_UNINSTALLED        | Triggered when a tag gets uninstalled                 |
-|                  | TAG_OPEN               | Triggered when an open event of a tag was detected    |
+|                  | HUMAN                  | Triggered when a human (or person) was detected                                                                                                                                  |
+|                  | ANIMAL                 | Triggered when an animal was detected                                                                                                                                            |
+|                  | MOVEMENT               | Triggered when an unspecified movement was detected                                                                                                                              |
+|                  | VEHICLE                | Triggered when a vehicle was detected                                                                                                                                            |
+| welcomeHomeEvent |                        | A welcome home event is triggered directly via a configured webhook                                                                                                              |
+|                  | PERSON                 | Triggered when a concrete person was detected                                                                                                                                    |
+|                  | PERSON_AWAY            | Triggered when a concrete person leaves                                                                                                                                          |  
+|                  | MOVEMENT               | Triggered when a movement was detected                                                                                                                                           |
+|                  | CONNECTION             | Triggered when a camera connection gets created                                                                                                                                  |   
+|                  | DISCONNECTION          | Triggered when a camera connection got lost                                                                                                                                      |    
+|                  | ON                     | Triggered when camera monitoring is switched on                                                                                                                                  |    
+|                  | OFF                    | Triggered when camera monitoring is switched off                                                                                                                                 |    
+|                  | BOOT                   | Triggered when a camera is booting                                                                                                                                               |    
+|                  | SD                     | Triggered when a camera SD card status was changed                                                                                                                               |    
+|                  | ALIM                   | Triggered when a power supply status was changed                                                                                                                                 |   
+|                  | NEW_MODULE             | Triggered when a new module was discovered                                                                                                                                       |    
+|                  | MODULE_CONNECT         | Triggered when a module gets connected                                                                                                                                           |    
+|                  | MODULE_DISCONNECT      | Triggered when a module gets disconnected                                                                                                                                        |   
+|                  | MODULE_LOW_BATTERY     | Triggered when the battery of a module gets low                                                                                                                                  | 
+|                  | MODULE_END_UPDATE      | Triggered when a firmware update of a module is done                                                                                                                             | 
+|                  | TAG_BIG_MOVE           | Triggered when a big movement of a tag was detected                                                                                                                              |  
+|                  | TAG_SMALL_MOVE         | Triggered when a small movement of a tag was detected                                                                                                                            |  
+|                  | TAG_UNINSTALLED        | Triggered when a tag gets uninstalled                                                                                                                                            |  
+|                  | TAG_OPEN               | Triggered when an open event of a tag was detected                                                                                                                               |
 
 ### Welcome and Presence Camera
 
@@ -519,33 +429,61 @@ Warnings:
 
 **Supported channels for the Welcome Camera thing:**
 
-| Channel ID                  | Item Type | Read/Write | Description                                                  |
-|-----------------------------|-----------|------------|--------------------------------------------------------------|
-| welcomeCameraStatus         | Switch    | Read-write | State of the camera (video surveillance on/off)              |
-| welcomeCameraSdStatus       | Switch    | Read-only  | State of the SD card                                         |
-| welcomeCameraAlimStatus     | Switch    | Read-only  | State of the power connector                                 |
-| welcomeCameraIsLocal        | Switch    | Read-only  | indicates whether the camera is on the same network than the openHAB Netatmo Binding |
-| welcomeCameraLivePicture    | Image     | Read-only  | Camera Live Snapshot                                         |
-| welcomeCameraLivePictureUrl | String    | Read-only  | Url of the live snapshot for this camera                     |
-| welcomeCameraLiveStreamUrl  | String    | Read-only  | Url of the live stream for this camera                       |
+| Channel Group  | Channel ID           | Item Type    | Read/Write | Description                                                                                                                                 |
+|----------------|----------------------|--------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------|
+| status         | monitoring           | Switch       | Read-write | State of the camera (video surveillance on/off)                                                                                             |
+| status         | sd-card              | String       | Read-only  | State of the SD card                                                                                                                        |
+| status         | alim                 | String       | Read-only  | State of the power connector                                                                                                                |
+| live           | picture              | Image        | Read-only  | Camera Live Snapshot                                                                                                                        |
+| live           | local-picture-url    | String       | Read-only  | Local Url of the live snapshot for this camera                                                                                              |
+| live           | vpn-picture-url      | String       | Read-only  | Url of the live snapshot for this camera through Netatmo VPN.                                                                               |
+| live           | local-stream-url (*) | String       | Read-only  | Local Url of the live stream for this camera (accessible if openhab server and camera are located on the same lan.                          |
+| live           | vpn-stream-url (*)   | String       | Read-only  | Url of the live stream for this camera through Netatmo VPN.                                                                                 |
+| signal         | strength             | Number       | Read-only  | Signal strength (0 for no signal, 1 for weak...)                                                                                            |
+| signal         | value                | Number:Power | Read-only  | Signal strength in dBm                                                                                                                      |
+| last-event     | type                 | String       | Read-only  | Type of event                                                                                                                               |
+| last-event     | subtype              | String       | Read-only  | Sub-type of event                                                                                                                           |
+| last-event     | time                 | DateTime     | Read-only  | Time of occurrence of event                                                                                                                 |
+| last-event     | message              | String       | Read-only  | Message sent by Netatmo corresponding to given event                                                                                        |
+| last-event     | snapshot             | Image        | Read-only  | picture of the last event, if it applies                                                                                                    |
+| last-event     | snapshot-url         | String       | Read-only  | If the last event (depending upon event type) in the home lead a snapshot picture, the picture URL will be available here                   |
+| last-event     | local-video-url      | String       | Read-only  | If the last event (depending upon event type) in the home lead a snapshot picture, the corresponding local video URL will be available here |
+| last-event     | vpn-video-url        | String       | Read-only  | If the last event (depending upon event type) in the home lead a snapshot picture, the corresponding VPN video URL will be available here   |
+| last-event     | video-status         | String       | Read-only  | Status of the video (recording, deleted or available)                                                                                       |
+| last-event     | person-id            | String       | Read-only  | Id of the person the event is about (if any)                                                                                                |
+
+(*) This channel is configurable : low, poor, high.
 
 **Supported channels for the Presence Camera thing:**
 
 Warnings:
 
-- The floodlight auto-mode (cameraFloodlightAutoMode) isn't updated it is changed by another application. Therefore the binding handles its own state of the auto-mode. This has the advantage that the user can define its own floodlight switch off behaviour.
-
-| Channel ID                  | Item Type | Read/Write | Description                                                  |
-|-----------------------------|-----------|------------|--------------------------------------------------------------|
-| cameraStatus                | Switch    | Read-write | State of the camera (video surveillance on/off)              |
-| cameraSdStatus              | Switch    | Read-only  | State of the SD card                                         |
-| cameraAlimStatus            | Switch    | Read-only  | State of the power connector                                 |
-| cameraIsLocal               | Switch    | Read-only  | indicates whether the camera is on the same network than the openHAB Netatmo Binding |
-| cameraLivePicture           | Image     | Read-only  | Camera Live Snapshot                                         |
-| cameraLivePictureUrl        | String    | Read-only  | Url of the live snapshot for this camera                     |
-| cameraLiveStreamUrl         | String    | Read-only  | Url of the live stream for this camera                       |
-| cameraFloodlightAutoMode    | Switch    | Read-write | When set the floodlight gets switched to auto instead of off |
-| cameraFloodlight            | Switch    | Read-write | Switch for the floodlight                                    |
+- The floodlight auto-mode (auto-mode) isn't updated it is changed by another application. Therefore the binding handles its own state of the auto-mode. This has the advantage that the user can define its own floodlight switch off behaviour.
+
+| Channel Group  | Channel ID           | Item Type    | Read/Write | Description                                                                                                                                 |
+|----------------|----------------------|--------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------|
+| status         | monitoring           | Switch       | Read-write | State of the camera (video surveillance on/off)                                                                                             |
+| status         | sd-card              | String       | Read-only  | State of the SD card                                                                                                                        |
+| status         | alim                 | String       | Read-only  | State of the power connector                                                                                                                |
+| live           | picture              | Image        | Read-only  | Camera Live Snapshot                                                                                                                        |
+| live           | picture-url          | String       | Read-only  | Url of the live snapshot for this camera                                                                                                    |
+| live           | local-stream-url (*) | String       | Read-only  | Local Url of the live stream for this camera (accessible if openhab server and camera are located on the same lan.                          |
+| live           | vpn-stream-url (*)   | String       | Read-only  | Url of the live stream for this camera through Netatmo VPN.                                                                                 |
+| signal         | strength             | Number       | Read-only  | Signal strength (0 for no signal, 1 for weak...)                                                                                            |
+| signal         | value                | Number:Power | Read-only  | Signal strength in dBm                                                                                                                      |
+| presence       | floodlight           | Switch       | Read-write | Sets the floodlight to ON/OFF/AUTO                                                                                                          |
+| last-event     | type                 | String       | Read-only  | Type of event                                                                                                                               |
+| last-event     | subtype              | String       | Read-only  | Sub-type of event                                                                                                                           |
+| last-event     | time                 | DateTime     | Read-only  | Time of occurrence of event                                                                                                                 |
+| last-event     | message              | String       | Read-only  | Message sent by Netatmo corresponding to given event                                                                                        |
+| last-event     | snapshot             | Image        | Read-only  | picture of the last event, if it applies                                                                                                    |
+| last-event     | snapshot-url         | String       | Read-only  | if the last event (depending upon event type) in the home lead a snapshot picture, the picture URL will be available here                   |
+| last-event     | local-video-url      | String       | Read-only  | If the last event (depending upon event type) in the home lead a snapshot picture, the corresponding local video URL will be available here |
+| last-event     | vpn-video-url        | String       | Read-only  | If the last event (depending upon event type) in the home lead a snapshot picture, the corresponding VPN video URL will be available here   |
+| last-event     | video-status         | String       | Read-only  | Status of the video (recording, deleted or available)                                                                                       |
+| last-event     | person-id            | String       | Read-only  | Id of the person the event is about (if any)                                                                                                |
+
+(*) This channel is configurable : low, poor, high.
 
 
 ### Welcome Person
@@ -559,18 +497,21 @@ Person things are automatically created in discovery process for all known perso
 
 **Supported channels for the Person thing:**
 
-| Channel ID                    | Item Type | Description                                            |
-|-------------------------------|-----------|--------------------------------------------------------|
-| welcomePersonLastSeen         | DateTime  | Time when this person was last seen                    |
-| welcomePersonAtHome           | Switch    | Indicates if this person is known to be at home or not |
-| welcomePersonAvatarUrl        | String    | URL for the avatar of this person                      |
-| welcomePersonAvatar           | Image     | Avatar of this person                                  |
-| welcomePersonLastEventMessage | String    | Last event message from this person                    |
-| welcomePersonLastEventTime    | DateTime  | Last event message time for this person                |
-| welcomePersonLastEventUrl     | String    | URL for the picture of the last event for this person  |
-| welcomePersonLastEvent        | Image     | Picture of the last event for this person              |
+| Channel Group  | Channel ID     | Item Type    | Description                                            |
+|----------------|----------------|--------------|--------------------------------------------------------|
+| person         | avatar-url     | String       | URL for the avatar of this person                      |
+| person         | avatar         | Image        | Avatar of this person                                  |
+| person         | at-home        | Switch       | Indicates if this person is known to be at home or not |
+| person         | last-seen      | DateTime     | Moment when this person was last seen                  |
+| person-event   | subtype        | String       | Sub-type of event                                      |
+| person-event   | message        | String       | Last event message from this person                    |
+| person-event   | time           | DateTime     | Moment of the last event for this person               |
+| person-event   | snapshot       | Image        | Picture of the last event for this person              |
+| person-event   | snapshot-url   | String       | URL for the picture of the last event for this person  |
+| person-event   | camera-id      | String       | ID of the camera that triggered the event              |
+
+All these channels except at-home are read only.
 
-All these channels except welcomePersonAtHome are read only.
 
 # Configuration Examples
 
@@ -578,114 +519,72 @@ All these channels except welcomePersonAtHome are read only.
 ## things/netatmo.things
 
 ```
-// Bridge configuration:
-Bridge netatmo:netatmoapi:home "Netatmo API" [ clientId="*********", clientSecret="**********", username = "mail@example.com", password = "******", readStation=true, readThermostat=false] {
-    // Thing configuration:
-    Thing NAMain inside "Netatmo Inside"       [ id="aa:aa:aa:aa:aa:aa" ]
-    Thing NAModule1 outside "Netatmo Outside"  [ id="bb:bb:bb:bb:bb:bb", parentId="aa:aa:aa:aa:aa:aa" ]
-    Thing NAModule3 rain "Netatmo Rain"        [ id="cc:cc:cc:cc:cc:cc", parentId="aa:aa:aa:aa:aa:aa" ]
+Bridge netatmo:account:home "Netatmo Account" [clientId="", clientSecret="", username="", password=""] {
+    Bridge weather-station inside "Inside Weather Station" [id="70:ee:aa:aa:aa:aa"] {
+        outdoor outside   "Outside Module" [id="02:00:00:aa:aa:aa"] {
+            Channels:
+                Type hum-measurement : maxHumWeek [limit="MAX",period="1week"]
+        }
+        rain rainModule        "Rain Module"    [id="05:00:00:aa:aa:aa"] {
+            Channels:
+                Type sum_rain-measurement: rainThisWeek  "Rain This Week"     [period="1week"]
+                Type sum_rain-measurement: rainThisMonth "Rain This Month"    [period="1month"]
+        }
+    }
 }
 ```
 
+
+## Sample configuration of live-stream-url channels:
+
+```
+        ....
+        Thing welcome camera "Caméra" [ id="xxxxxx" ] {
+            Channels:
+                Type live-stream-url : live#local-stream-url [ quality="high" ]
+                Type live-stream-url : live#vpn-stream-url [ quality="low" ]
+        }
+        ...
+```
+
+
 ## items/netatmo.items
 
 ```
 # Indoor Module
-Number:Temperature   Indoor_Temp                       "Temperature [%.1f %unit%]"                                  <temperature>      { channel = "netatmo:NAMain:home:inside:Temperature" }
-Number:Temperature   Indoor_Min_Temp                   "Min Temperature Today [%.1f %unit%]"                        <temperature>      { channel = "netatmo:NAMain:home:inside:MinTemp" }
-Number:Temperature   Indoor_Min_Temp_This_Week         "Min Temperature This Week [%.1f %unit%]"                    <temperature>      { channel = "netatmo:NAMain:home:inside:MinTempThisWeek" }
-Number:Temperature   Indoor_Min_Temp_This_Month        "Min Temperature This Month [%.1f %unit%]"                   <temperature>      { channel = "netatmo:NAMain:home:inside:MinTempThisMonth" }
-Number:Temperature   Indoor_Max_Temp                   "Max Temperature Today [%.1f %unit%]"                        <temperature>      { channel = "netatmo:NAMain:home:inside:MaxTemp" }
-Number:Temperature   Indoor_Max_Temp_This_Week         "Max Temperature This Week [%.1f %unit%]"                    <temperature>      { channel = "netatmo:NAMain:home:inside:MaxTempThisWeek" }
-Number:Temperature   Indoor_Max_Temp_This_Month        "Max Temperature This Month [%.1f %unit%]"                     <temperature>      { channel = "netatmo:NAMain:home:inside:MaxTempThisMonth" }
-DateTime             Indoor_Min_Temp_TS                "Min Temperature Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"      <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinTemp" }
-DateTime             Indoor_Min_Temp_This_Week_TS      "Min Temperature This Week  [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinTempThisWeek" }
-DateTime             Indoor_Min_Temp_This_Month_TS     "Min Temperature This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinTempThisMonth" }
-DateTime             Indoor_Max_Temp_TS                "Max Temperature Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"      <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxTemp" }
-DateTime             Indoor_Max_Temp_This_Week_TS      "Max Temperature This Week  [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxTempThisWeek" }
-DateTime             Indoor_Max_Temp_This_Month_TS     "Max Temperature This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxTempThisMonth" }
-Number:Dimensionless Indoor_Humidity                   "Humidity [%d %unit%]"                                       <humidity>         { channel = "netatmo:NAMain:home:inside:Humidity" }
-Number:Dimensionless Indoor_Min_Humidity               "Min Humidity Today [%d %unit%]"                             <humidity>         { channel = "netatmo:NAMain:home:inside:MinHumidity" }
-Number:Dimensionless Indoor_Min_Humidity_This_Week     "Min Humidity This Week [%d %unit%]"                         <humidity>         { channel = "netatmo:NAMain:home:inside:MinHumidityThisWeek" }
-Number:Dimensionless Indoor_Min_Humidity_This_Month    "Min Humidity This Month [%d %unit%]"                        <humidity>         { channel = "netatmo:NAMain:home:inside:MinHumidityThisMonth" }
-Number:Dimensionless Indoor_Max_Humidity               "Max Humidity Today [%d %unit%]"                             <humidity>         { channel = "netatmo:NAMain:home:inside:MaxHumidity" }
-Number:Dimensionless Indoor_Max_Humidity_This_Week     "Max Humidity This Week [%d %unit%]"                         <humidity>         { channel = "netatmo:NAMain:home:inside:MaxHumidityThisWeek" }
-Number:Dimensionless Indoor_Max_Humidity_This_Month    "Max Humidity This Month [%d %unit%]"                        <humidity>         { channel = "netatmo:NAMain:home:inside:MaxHumidityThisMonth" }
-DateTime             Indoor_Min_Humidity_TS            "Min Humidity Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"         <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinHumidity" }
-DateTime             Indoor_Min_Humidity_This_Week_TS  "Min Humidity This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"     <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinHumidityThisWeek" }
-DateTime             Indoor_Min_Humidity_This_Month_TS "Min Humidity This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"    <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinHumidityThisMonth" }
-DateTime             Indoor_Max_Humidity_TS            "Max Humidity Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"         <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxHumidity" }
-DateTime             Indoor_Max_Humidity_This_Week_TS  "Max Humidity This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"     <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxHumidityThisWeek" }
-DateTime             Indoor_Max_Humidity_This_Month_TS "Max Humidity This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"    <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxHumidityThisMonth" }
-Number               Indoor_Humidex                    "Humidex [%.0f]"                                             <temperature_hot>  { channel = "netatmo:NAMain:home:inside:Humidex" }
-Number:Temperature   Indoor_HeatIndex                  "HeatIndex [%.1f %unit%]"                                    <temperature_hot>  { channel = "netatmo:NAMain:home:inside:HeatIndex" }
-Number:Temperature   Indoor_Dewpoint                   "Dewpoint [%.1f %unit%]"                                     <temperature_cold> { channel = "netatmo:NAMain:home:inside:Dewpoint" }
-Number:Temperature   Indoor_DewpointDepression         "DewpointDepression [%.1f %unit%]"                           <temperature_cold> { channel = "netatmo:NAMain:home:inside:DewpointDepression" }
-Number:Dimensionless Indoor_Co2                        "CO2 [%d %unit%]"                                            <carbondioxide>    { channel = "netatmo:NAMain:home:inside:Co2" }
-Number:Dimensionless Indoor_Min_Co2                    "Min CO2 Today [%.1f %unit%]"                                <carbondioxide>    { channel = "netatmo:NAMain:home:inside:MinCo2" }
-Number:Dimensionless Indoor_Min_Co2_This_Week          "Min CO2 This Week [%.1f %unit%]"                            <carbondioxide>    { channel = "netatmo:NAMain:home:inside:MinCo2ThisWeek" }
-Number:Dimensionless Indoor_Min_Co2_This_Month         "Min CO2 This Month [%.1f %unit%]"                           <carbondioxide>    { channel = "netatmo:NAMain:home:inside:MinCo2ThisMonth" }
-Number:Dimensionless Indoor_Max_Co2                    "Max CO2 Today [%.1f %unit%]"                                <carbondioxide>    { channel = "netatmo:NAMain:home:inside:MaxCo2" }
-Number:Dimensionless Indoor_Max_Co2_This_Week          "Max CO2 This Week [%.1f %unit%]"                            <carbondioxide>    { channel = "netatmo:NAMain:home:inside:MaxCo2ThisWeek" }
-Number:Dimensionless Indoor_Max_Co2_This_Month         "Max CO2 This Month [%.1f %unit%]"                             <carbondioxide>    { channel = "netatmo:NAMain:home:inside:MaxCo2ThisMonth" }
-DateTime             Indoor_Min_Co2_TS                 "Min CO2 Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"              <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinCo2" }
-DateTime             Indoor_Min_Co2_This_Week_TS       "Min CO2 This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"          <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinCo2ThisWeek" }
-DateTime             Indoor_Min_Co2_This_Month_TS      "Min CO2 This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"         <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinCo2ThisMonth" }
-DateTime             Indoor_Max_Co2_TS                 "Max CO2 Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"              <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxCo2" }
-DateTime             Indoor_Max_Co2_This_Week_TS       "Max CO2 This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"          <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxCo2ThisWeek" }
-DateTime             Indoor_Max_Co2_This_Month_TS      "Max CO2 This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"         <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxCo2ThisMonth" }
-Number:Pressure      Indoor_Pressure                   "Pressure [%.1f %unit%]"                                     <pressure>         { channel = "netatmo:NAMain:home:inside:Pressure" }
-Number:Pressure      Indoor_Min_Pressure               "Min Pressure Today [%d %unit%]"                             <pressure>         { channel = "netatmo:NAMain:home:inside:MinPressure" }
-Number:Pressure      Indoor_Min_Pressure_This_Week     "Min Pressure This Week [%d %unit%]"                         <pressure>         { channel = "netatmo:NAMain:home:inside:MinPressureThisWeek" }
-Number:Pressure      Indoor_Min_Pressure_This_Month    "Min Pressure This Month [%d %unit%]"                        <pressure>         { channel = "netatmo:NAMain:home:inside:MinPressureThisMonth" }
-Number:Pressure      Indoor_Max_Pressure               "Max Pressure Today [%d %unit%]"                             <pressure>         { channel = "netatmo:NAMain:home:inside:MaxPressure" }
-Number:Pressure      Indoor_Max_Pressure_This_Week     "Max Pressure This Week [%d %unit%]"                         <pressure>         { channel = "netatmo:NAMain:home:inside:MaxPressureThisWeek" }
-Number:Pressure      Indoor_Max_Pressure_This_Month    "Max Pressure This Month [%d %unit%]"                        <pressure>         { channel = "netatmo:NAMain:home:inside:MaxPressureThisMonth" }
-DateTime             Indoor_Min_Pressure_TS            "Min Pressure Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"         <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinPressure" }
-DateTime             Indoor_Min_Pressure_This_Week_TS  "Min Pressure This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"     <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinPressureThisWeek" }
-DateTime             Indoor_Min_Pressure_This_Month_TS "Min Pressure This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"    <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinPressureThisMonth" }
-DateTime             Indoor_Max_Pressure_TS            "Max Pressure Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"         <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxPressure" }
-DateTime             Indoor_Max_Pressure_This_Week_TS  "Max Pressure This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"     <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxPressureThisWeek" }
-DateTime             Indoor_Max_Pressure_This_Month_TS "Max Pressure This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"    <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxPressureThisMonth" }
-Number:Pressure      Indoor_AbsolutePressure           "AbsolutePressure [%.1f %unit%]"                             <pressure>         { channel = "netatmo:NAMain:home:inside:AbsolutePressure" }
-Number:Dimensionless Indoor_Noise                      "Noise [%d %unit%]"                                          <soundvolume>      { channel = "netatmo:NAMain:home:inside:Noise" }
-Number:Dimensionless Indoor_Min_Noise                  "Min Noise Today [%.1f %unit%]"                              <soundvolume>      { channel = "netatmo:NAMain:home:inside:MinNoise" }
-Number:Dimensionless Indoor_Min_Noise_This_Week        "Min Noise This Week [%.1f %unit%]"                          <soundvolume>      { channel = "netatmo:NAMain:home:inside:MinNoiseThisWeek" }
-Number:Dimensionless Indoor_Min_Noise_This_Month       "Min Noise This Month [%.1f %unit%]"                         <soundvolume>      { channel = "netatmo:NAMain:home:inside:MinNoiseThisMonth" }
-Number:Dimensionless Indoor_Max_Noise                  "Max Noise Today [%.1f %unit%]"                              <soundvolume>      { channel = "netatmo:NAMain:home:inside:MaxNoise" }
-Number:Dimensionless Indoor_Max_Noise_This_Week        "Max Noise This Week [%.1f %unit%]"                          <soundvolume>      { channel = "netatmo:NAMain:home:inside:MaxNoiseThisWeek" }
-Number:Dimensionless Indoor_Max_Noise_This_Month       "Max Noise This Month [%.1f %unit%]"                         <soundvolume>      { channel = "netatmo:NAMain:home:inside:MaxNoiseThisMonth" }
-DateTime             Indoor_Min_Noise_TS               "Min Noise Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"            <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinNoise" }
-DateTime             Indoor_Min_Noise_This_Week_TS     "Min Noise This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"        <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinNoiseThisWeek" }
-DateTime             Indoor_Min_Noise_This_Month_TS    "Min Noise This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"       <calendar>         { channel = "netatmo:NAMain:home:inside:DateMinNoiseThisMonth" }
-DateTime             Indoor_Max_Noise_TS               "Max Noise Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"            <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxNoise" }
-DateTime             Indoor_Max_Noise_This_Week_TS     "Max Noise This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"        <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxNoiseThisWeek" }
-DateTime             Indoor_Max_Noise_This_Month_TS    "Max Noise This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"       <calendar>         { channel = "netatmo:NAMain:home:inside:DateMaxNoiseThisMonth" }
-Number               Indoor_WifiStatus                 "WifiStatus [%s]"                                            <signal>           { channel = "netatmo:NAMain:home:inside:WifiStatus" }
-DateTime             Indoor_TimeStamp                  "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"                  <calendar>         { channel = "netatmo:NAMain:home:inside:TimeStamp" }
-Location             Indoor_Location                   "Location"                                                   <movecontrol>      { channel = "netatmo:NAMain:home:inside:Location" }
-DateTime             Indoor_LastStatusStore            "LastStatusStore [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"            <text>             { channel = "netatmo:NAMain:home:inside:LastStatusStore" }
+Number:Temperature   Indoor_Temp                       "Temperature [%.1f %unit%]"                                  <temperature>      { channel = "netatmo:weather-station:home:inside:temperature#value" }
+Number:Temperature   Indoor_Min_Temp                   "Min Temperature Today [%.1f %unit%]"                        <temperature>      { channel = "netatmo:weather-station:home:inside:temperature#min-today" }
+Number:Temperature   Indoor_Max_Temp                   "Max Temperature Today [%.1f %unit%]"                        <temperature>      { channel = "netatmo:weather-station:home:inside:temperature#max-today" }
+Number:Dimensionless Indoor_Humidity                   "Humidity [%d %unit%]"                                       <humidity>         { channel = "netatmo:weather-station:home:inside:humidity#value" }
+Number               Indoor_Humidex                    "Humidex [%.0f]"                                             <temperature_hot>  { channel = "netatmo:weather-station:home:inside:humidity#humidex" }
+Number:Temperature   Indoor_HeatIndex                  "HeatIndex [%.1f %unit%]"                                    <temperature_hot>  { channel = "netatmo:weather-station:home:inside:temperature#heat-index" }
+Number:Temperature   Indoor_Dewpoint                   "Dewpoint [%.1f %unit%]"                                     <temperature_cold> { channel = "netatmo:weather-station:home:inside:temperature#dewpoint" }
+Number:Temperature   Indoor_DewpointDepression         "DewpointDepression [%.1f %unit%]"                           <temperature_cold> { channel = "netatmo:weather-station:home:inside:temperature#dewpoint-depression" }
+Number:Dimensionless Indoor_Co2                        "CO2 [%d %unit%]"                                            <carbondioxide>    { channel = "netatmo:weather-station:home:inside:airquality#co2" }
+Number:Pressure      Indoor_Pressure                   "Pressure [%.1f %unit%]"                                     <pressure>         { channel = "netatmo:weather-station:home:inside:pressure#value" }
+Number:Pressure      Indoor_AbsolutePressure           "AbsolutePressure [%.1f %unit%]"                             <pressure>         { channel = "netatmo:weather-station:home:inside:pressure#absolute" }
+Number:Dimensionless Indoor_Noise                      "Noise [%d %unit%]"                                          <soundvolume>      { channel = "netatmo:weather-station:home:inside:noise#value" }
+Number               Indoor_RadioStatus                 "RadioStatus [%s]"                                          <signal>           { channel = "netatmo:weather-station:home:inside:signal#strength" }
+DateTime             Indoor_TimeStamp                  "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"                  <calendar>         { channel = "netatmo:weather-station:home:inside:timestamp#measures" }
+DateTime             Indoor_LastSeen            "LastSeen [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"                          <text>             { channel = "netatmo:weather-station:home:inside:timestamp#last-seen" }
 
 # Outdoor Module
-Number:Temperature   Outdoor_Temperature               "Temperature [%.1f %unit%]"                                  <temperature>      { channel = "netatmo:NAModule1:home:outside:Temperature" }
-String               Outdoor_TempTrend                 "TempTrend [%s]"                                             <line>             { channel = "netatmo:NAModule1:home:outside:TempTrend" }
-Number:Dimensionless Outdoor_Humidity                  "Humidity [%d %unit%]"                                       <humidity>         { channel = "netatmo:NAModule1:home:outside:Humidity" }
-Number               Outdoor_Humidex                   "Humidex [%.0f]"                                             <temperature_hot>  { channel = "netatmo:NAModule1:home:outside:Humidex" }
-Number:Temperature   Outdoor_HeatIndex                 "HeatIndex [%.1f %unit%]"                                    <temperature_hot>  { channel = "netatmo:NAModule1:home:outside:HeatIndex" }
-Number:Temperature   Outdoor_Dewpoint                  "Dewpoint [%.1f %unit%]"                                     <temperature_cold> { channel = "netatmo:NAModule1:home:outside:Dewpoint" }
-Number:Temperature   Outdoor_DewpointDepression        "DewpointDepression [%.1f %unit%]"                           <temperature_cold> { channel = "netatmo:NAModule1:home:outside:DewpointDepression" }
-Number               Outdoor_RfStatus                  "RfStatus [%.0f / 5]"                                        <signal>           { channel = "netatmo:NAModule1:home:outside:RfStatus" }
-Switch               Outdoor_LowBattery                "LowBattery [%s]"                                            <siren>            { channel = "netatmo:NAModule1:home:outside:LowBattery" }
-Number               Outdoor_BatteryVP                 "BatteryVP [%.0f %%]"                                        <battery>          { channel = "netatmo:NAModule1:home:outside:BatteryVP" }
-DateTime             Outdoor_TimeStamp                 "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"                  <calendar>         { channel = "netatmo:NAModule1:home:outside:TimeStamp" }
-DateTime             Outdoor_LastMessage               "LastMessage [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"                <text>             { channel = "netatmo:NAModule1:home:outside:LastMessage" }
+Number:Temperature   Outdoor_Temperature               "Temperature [%.1f %unit%]"                                  <temperature>      { channel = "netatmo:outdoor:home:inside:outside:temperature#value" }
+String               Outdoor_TempTrend                 "TempTrend [%s]"                                             <line>             { channel = "netatmo:outdoor:home:inside:outside:temperature#trend" }
+Number:Dimensionless Outdoor_Humidity                  "Humidity [%d %unit%]"                                       <humidity>         { channel = "netatmo:outdoor:home:inside:outside:humidity#value" }
+Number               Outdoor_Humidex                   "Humidex [%.0f]"                                             <temperature_hot>  { channel = "netatmo:outdoor:home:inside:outside:humidity#humidex" }
+Number:Temperature   Outdoor_HeatIndex                 "heat-index [%.1f %unit%]"                                   <temperature_hot>  { channel = "netatmo:outdoor:home:inside:outside:temperature#heat-index" }
+Number:Temperature   Outdoor_Dewpoint                  "Dewpoint [%.1f %unit%]"                                     <temperature_cold> { channel = "netatmo:outdoor:home:inside:outside:temperature#dewpoint" }
+Number:Temperature   Outdoor_DewpointDepression        "DewpointDepression [%.1f %unit%]"                           <temperature_cold> { channel = "netatmo:outdoor:home:inside:outside:temperature#dewpoint-depression" }
+Number               Outdoor_RadioStatus               "RfStatus [%.0f / 5]"                                        <signal>           { channel = "netatmo:outdoor:home:inside:outside:signal#strength" }
+Switch               Outdoor_LowBattery                "LowBattery [%s]"                                            <siren>            { channel = "netatmo:outdoor:home:inside:outside:battery#low-battery" }
+DateTime             Outdoor_TimeStamp                 "Measures TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"         <calendar>         { channel = "netatmo:outdoor:home:inside:outside:timestamp#measures" }
+DateTime             Outdoor_LastMessage               "LastMessage [%1$td.%1$tm.%1$tY %1$tH:%1$tM]"                <text>             { channel = "netatmo:outdoor:home:inside:outside:timestamp#last-seen" }
 
 # Rain Module
-Number:Length        Rain_Hour                         "Rain Last Hour [%.02f %unit%]"                              <rain>             {channel="netatmo:NAModule3:home:rain:SumRain1"}
-Number:Length        Rain_Today                        "Rain Today [%.02f %unit%]"                                  <rain>             {channel="netatmo:NAModule3:home:rain:SumRain24"}
-Number:Length        Rain_Week                         "Rain This Week [%.02f %unit%]"                              <rain>             {channel="netatmo:NAModule3:home:rain:SumRainThisWeek"}
-Number:Length        Rain_Month                        "Rain This Month [%.02f %unit%]"                             <rain>             {channel="netatmo:NAModule3:home:rain:SumRainThisMonth"}
-Number               Rain_BatteryVP                    "Rain battery status [%d%%]"                                 <battery>          {channel="netatmo:NAModule3:home:rain:BatteryVP"}
+Number:Speed         Rain_Intensity                    "Rain Intensity [%.1f %unit%]"                               <rain>             { channel = "netatmo:rain:home:inside:rainModule:rain#value"}
+Number:Length        Rain_Hour                         "Rain Last Hour [%.1f %unit%]"                               <rain>             { channel = "netatmo:rain:home:inside:rainModule:rain#sum-1"}
+Number:Length        Rain_Today                        "Rain Today [%.1f %unit%]"                                   <rain>             { channel = "netatmo:rain:home:inside:rainModule:rain#sum-24"}
 ```
 
 ## sitemaps/netatmo.sitemap
@@ -695,80 +594,23 @@ sitemap netatmo label="Netatmo" {
     Frame label="Indoor" {
         Text item=Indoor_Temp
         Text item=Indoor_Min_Temp
-        Text item=Indoor_Min_Temp_This_Week
-        Text item=Indoor_Min_Temp_This_Month
         Text item=Indoor_Max_Temp
-        Text item=Indoor_Max_Temp_This_Week
-        Text item=Indoor_Max_Temp_This_Month
         Text item=Indoor_Min_Temp_TS
-        Text item=Indoor_Min_Temp_This_Week_TS
-        Text item=Indoor_Min_Temp_This_Month_TS
         Text item=Indoor_Max_Temp_TS
-        Text item=Indoor_Max_Temp_This_Week_TS
-        Text item=Indoor_Max_Temp_This_Month_TS
         Text item=Indoor_Humidity
-        Text item=Indoor_Min_Humidity
-        Text item=Indoor_Min_Humidity_This_Week
-        Text item=Indoor_Min_Humidity_This_Month
-        Text item=Indoor_Max_Humidity
-        Text item=Indoor_Max_Humidity_This_Week
-        Text item=Indoor_Max_Humidity_This_Month
-        Text item=Indoor_Min_Humidity_TS
-        Text item=Indoor_Min_Humidity_This_Week_TS
-        Text item=Indoor_Min_Humidity_This_Month_TS
-        Text item=Indoor_Max_Humidity_TS
-        Text item=Indoor_Max_Humidity_This_Week_TS
-        Text item=Indoor_Max_Humidity_This_Month_TS
         Text item=Indoor_Humidex                     valuecolor=[<20.1="green",<29.1="blue",<28.1="yellow",<45.1="orange",<54.1="red",>54.1="maroon"]
         Text item=Indoor_HeatIndex
         Text item=Indoor_Dewpoint
         Text item=Indoor_DewpointDepression
         Text item=Indoor_Co2                        valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"]
-        Text item=Indoor_Min_Co2                    valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"]
-        Text item=Indoor_Min_Co2_This_Week          valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"]
-        Text item=Indoor_Min_Co2_This_Month         valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"]
-        Text item=Indoor_Max_Co2                    valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"]
-        Text item=Indoor_Max_Co2_This_Week          valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"]
-        Text item=Indoor_Max_Co2_This_Month         valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"]
-        Text item=Indoor_Min_Co2_TS
-        Text item=Indoor_Min_Co2_This_Week_TS
-        Text item=Indoor_Min_Co2_This_Month_TS
-        Text item=Indoor_Max_Co2_TS
-        Text item=Indoor_Max_Co2_This_Week_TS
-        Text item=Indoor_Max_Co2_This_Month_TS
         Text item=Indoor_Pressure
-        Text item=Indoor_Min_Pressure
-        Text item=Indoor_Min_Pressure_This_Week
-        Text item=Indoor_Min_Pressure_This_Month
-        Text item=Indoor_Max_Pressure
-        Text item=Indoor_Max_Pressure_This_Week
-        Text item=Indoor_Max_Pressure_This_Month
-        Text item=Indoor_Min_Pressure_TS
-        Text item=Indoor_Min_Pressure_This_Week_TS
-        Text item=Indoor_Min_Pressure_This_Month_TS
-        Text item=Indoor_Max_Pressure_TS
-        Text item=Indoor_Max_Pressure_This_Week_TS
-        Text item=Indoor_Max_Pressure_This_Month_TS
         Text item=Indoor_AbsolutePressure
         Text item=Indoor_Noise
-        Text item=Indoor_Min_Noise
-        Text item=Indoor_Min_Noise_This_Week
-        Text item=Indoor_Min_Noise_This_Month
-        Text item=Indoor_Max_Noise
-        Text item=Indoor_Max_Noise_This_Week
-        Text item=Indoor_Max_Noise_This_Month
-        Text item=Indoor_Min_Noise_TS
-        Text item=Indoor_Min_Noise_This_Week_TS
-        Text item=Indoor_Min_Noise_This_Month_TS
-        Text item=Indoor_Max_Noise_TS
-        Text item=Indoor_Max_Noise_This_Week_TS
-        Text item=Indoor_Max_Noise_This_Month_TS
         Text item=Indoor_WifiStatus
         Text item=Indoor_TimeStamp
-        Text item=Indoor_Location
-        Text item=Indoor_LastStatusStore
+        Text item=Indoor_LastSeen
     }
-    Frame label="Outdoor" {
+    Frame label="Outdoor" { 
         Text item=Outdoor_Temperature
         Text item=Outdoor_TempTrend
         Text item=Outdoor_Humidity
@@ -776,13 +618,14 @@ sitemap netatmo label="Netatmo" {
         Text item=Outdoor_HeatIndex
         Text item=Outdoor_Dewpoint
         Text item=Outdoor_DewpointDepression
-        Text item=Outdoor_RfStatus
+        Text item=Outdoor_RadioStatus
         Text item=Outdoor_LowBattery
         Text item=Outdoor_BatteryVP
         Text item=Outdoor_TimeStamp
         Text item=Outdoor_LastMessage
     }
     Frame label="Rain" {
+        Text item=Rain_Intensity
         Text item=Rain_Hour
         Text item=Rain_Today
         Text item=Rain_Week
@@ -796,9 +639,7 @@ sitemap netatmo label="Netatmo" {
 # Sample data
 
 If you want to evaluate this binding but have not got a Netatmo station yourself
-yet, you can add the Netatmo office in Paris to your account:
-
-https://www.netatmo.com/en-US/addguest/index/TIQ3797dtfOmgpqUcct3/70:ee:50:00:02:20
+yet, you can search on the web for a publicly shared weather station.
 
 
 # Icons
@@ -837,3 +678,5 @@ The following icons are used by original Netatmo web app:
 - https://my.netatmo.com/images/my/app/wifi_medium.png
 - https://my.netatmo.com/images/my/app/wifi_high.png
 - https://my.netatmo.com/images/my/app/wifi_full.png
+
+
index 0c0e870cf407dfa063313aab2dfa0ed7f057f028..b6f562b57c810c344438805c623115775425bba7 100644 (file)
 
   <name>openHAB Add-ons :: Bundles :: Netatmo Binding</name>
 
-  <properties>
-    <bnd.importpackage>!android.*,!com.android.org.*,!org.apache.harmony.*,!sun.*,!org.apache.oltu.*</bnd.importpackage>
-  </properties>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.openhab.osgiify</groupId>
-      <artifactId>org.json.json</artifactId>
-      <version>20131018</version>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.squareup.okhttp</groupId>
-      <artifactId>okhttp</artifactId>
-      <version>2.7.5</version>
-      <scope>compile</scope>
-      <exclusions>
-        <exclusion>
-          <groupId>com.google.android</groupId>
-          <artifactId>*</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-    <dependency>
-      <groupId>com.squareup.okhttp</groupId>
-      <artifactId>logging-interceptor</artifactId>
-      <version>2.7.5</version>
-      <scope>compile</scope>
-      <exclusions>
-        <exclusion>
-          <groupId>com.google.android</groupId>
-          <artifactId>*</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-    <dependency>
-      <groupId>com.squareup.okio</groupId>
-      <artifactId>okio</artifactId>
-      <version>1.6.0</version>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>io.gsonfire</groupId>
-      <artifactId>gson-fire</artifactId>
-      <version>1.8.4</version>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.oltu.oauth2</groupId>
-      <artifactId>org.apache.oltu.oauth2.client</artifactId>
-      <version>1.0.0</version>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.oltu.oauth2</groupId>
-      <artifactId>org.apache.oltu.oauth2.common</artifactId>
-      <version>1.0.0</version>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>commons-codec</groupId>
-      <artifactId>commons-codec</artifactId>
-      <version>1.8</version>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.google.code.gson</groupId>
-      <artifactId>gson</artifactId>
-      <version>2.8.5</version>
-      <scope>compile</scope>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>io.swagger.codegen.v3</groupId>
-        <artifactId>swagger-codegen-maven-plugin</artifactId>
-        <version>3.0.21</version>
-        <executions>
-          <execution>
-            <goals>
-              <goal>generate</goal>
-            </goals>
-            <configuration>
-              <inputSpec>https://raw.githubusercontent.com/cbornet/netatmo-swagger-decl/35e27745fb0d432bc6c8b5ec7a83ed2a09944cea/spec/swagger.yaml</inputSpec>
-              <language>java</language>
-              <generateApiTests>false</generateApiTests>
-              <generateModelTests>false</generateModelTests>
-              <configOptions>
-                <sourceFolder>src/main/java</sourceFolder>
-                <java8>true</java8>
-                <dateLibrary>java8-localdatetime</dateLibrary>
-                <useRuntimeException>true</useRuntimeException>
-              </configOptions>
-            </configuration>
-          </execution>
-        </executions>
-        <dependencies>
-          <dependency>
-            <!-- Required for JDK 17 compatibility, see: https://github.com/swagger-api/swagger-codegen/issues/11253 -->
-            <groupId>com.github.jknack</groupId>
-            <artifactId>handlebars</artifactId>
-            <version>4.3.0</version>
-          </dependency>
-        </dependencies>
-      </plugin>
-    </plugins>
-  </build>
-
 </project>
index 21b41c9cde3eeede333c7261c0b986d6112ee3f5..e031c4791014d1d1b1e2d994dddf02f6f11288b5 100644 (file)
@@ -4,7 +4,6 @@
 
        <feature name="openhab-binding-netatmo" description="Netatmo Binding" version="${project.version}">
                <feature>openhab-runtime-base</feature>
-               <bundle dependency="true">mvn:org.openhab.osgiify/org.json.json/20131018</bundle>
                <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.netatmo/${project.version}</bundle>
        </feature>
 </features>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/APIUtils.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/APIUtils.java
deleted file mode 100644 (file)
index 5fd5a1e..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * {@link APIUtils} provides util methods for the usage of the generated API classes.
- *
- * @author Sven Strohschein - Initial contribution
- */
-@NonNullByDefault
-public final class APIUtils {
-
-    private APIUtils() {
-    }
-
-    public static <T> Stream<T> nonNullStream(Collection<T> collection) {
-        return Optional.ofNullable(collection).stream().flatMap(Collection::stream);
-    }
-
-    public static <T> List<T> nonNullList(List<T> list) {
-        return Optional.ofNullable(list).orElse(Collections.emptyList());
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/ChannelTypeUtils.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/ChannelTypeUtils.java
deleted file mode 100644 (file)
index 97b6a1c..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-
-import javax.measure.Unit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.io.net.http.HttpUtil;
-import org.openhab.core.library.types.DateTimeType;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.RawType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-/**
- * This class holds various channel values conversion methods
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class ChannelTypeUtils {
-
-    public static State toStringType(@Nullable String value) {
-        return (value == null) ? UnDefType.NULL : new StringType(value);
-    }
-
-    public static ZonedDateTime toZonedDateTime(Integer netatmoTS, ZoneId zoneId) {
-        Instant i = Instant.ofEpochSecond(netatmoTS);
-        return ZonedDateTime.ofInstant(i, zoneId);
-    }
-
-    public static State toDateTimeType(@Nullable Float netatmoTS, ZoneId zoneId) {
-        return netatmoTS == null ? UnDefType.NULL : toDateTimeType(toZonedDateTime(netatmoTS.intValue(), zoneId));
-    }
-
-    public static State toDateTimeType(@Nullable Integer netatmoTS, ZoneId zoneId) {
-        return netatmoTS == null ? UnDefType.NULL : toDateTimeType(toZonedDateTime(netatmoTS, zoneId));
-    }
-
-    public static State toDateTimeType(@Nullable ZonedDateTime zonedDateTime) {
-        return (zonedDateTime == null) ? UnDefType.NULL : new DateTimeType(zonedDateTime);
-    }
-
-    public static State toDecimalType(@Nullable Float value) {
-        return (value == null) ? UnDefType.NULL : toDecimalType(new BigDecimal(value));
-    }
-
-    public static State toDecimalType(@Nullable Integer value) {
-        return (value == null) ? UnDefType.NULL : toDecimalType(new BigDecimal(value));
-    }
-
-    public static State toDecimalType(@Nullable Double value) {
-        return (value == null) ? UnDefType.NULL : toDecimalType(new BigDecimal(value));
-    }
-
-    public static State toDecimalType(float value) {
-        return toDecimalType(new BigDecimal(value));
-    }
-
-    public static State toDecimalType(double value) {
-        return toDecimalType(new BigDecimal(value));
-    }
-
-    public static State toDecimalType(@Nullable BigDecimal decimal) {
-        return decimal == null ? UnDefType.NULL : new DecimalType(decimal.setScale(2, RoundingMode.HALF_UP));
-    }
-
-    public static State toDecimalType(@Nullable String textualDecimal) {
-        return textualDecimal == null ? UnDefType.NULL : new DecimalType(textualDecimal);
-    }
-
-    public static State toOnOffType(@Nullable String yesno) {
-        return "on".equalsIgnoreCase(yesno) ? OnOffType.ON : OnOffType.OFF;
-    }
-
-    public static State toOnOffType(@Nullable Integer value) {
-        return value != null ? (value == 1 ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF;
-    }
-
-    public static State toOnOffType(@Nullable Boolean value) {
-        return value != null ? (value ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF;
-    }
-
-    public static State toQuantityType(@Nullable Float value, Unit<?> unit) {
-        return value == null ? UnDefType.NULL : toQuantityType(new BigDecimal(value), unit);
-    }
-
-    public static State toQuantityType(@Nullable Integer value, Unit<?> unit) {
-        return value == null ? UnDefType.NULL : toQuantityType(new BigDecimal(value), unit);
-    }
-
-    public static State toQuantityType(@Nullable Double value, Unit<?> unit) {
-        return value == null ? UnDefType.NULL : toQuantityType(new BigDecimal(value), unit);
-    }
-
-    public static State toQuantityType(float value, Unit<?> unit) {
-        return toQuantityType(new BigDecimal(value), unit);
-    }
-
-    public static State toQuantityType(int value, Unit<?> unit) {
-        return toQuantityType(new BigDecimal(value), unit);
-    }
-
-    public static State toQuantityType(double value, Unit<?> unit) {
-        return toQuantityType(new BigDecimal(value), unit);
-    }
-
-    public static State toQuantityType(@Nullable BigDecimal value, Unit<?> unit) {
-        return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
-    }
-
-    public static State toRawType(String pictureUrl) {
-        RawType picture = HttpUtil.downloadImage(pictureUrl);
-        return picture == null ? UnDefType.UNDEF : picture;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NATherm1StateDescriptionProvider.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NATherm1StateDescriptionProvider.java
deleted file mode 100644 (file)
index bd46b31..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.events.EventPublisher;
-import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
-import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
-import org.openhab.core.thing.link.ItemChannelLinkRegistry;
-import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-
-/**
- * Dynamic provider of state options for NATherm1Handler.
- *
- * @author Gregory Moyer - Initial contribution
- * @author Gaël L'hopital - Ported as-is in Netatmo binding
- */
-@Component(service = { DynamicStateDescriptionProvider.class, NATherm1StateDescriptionProvider.class })
-@NonNullByDefault
-public class NATherm1StateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
-
-    @Activate
-    public NATherm1StateDescriptionProvider(final @Reference EventPublisher eventPublisher, //
-            final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
-            final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
-        this.eventPublisher = eventPublisher;
-        this.itemChannelLinkRegistry = itemChannelLinkRegistry;
-        this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
-    }
-}
index 1a5dc1413f463d8b39c226c9d8c3ca6186b4e14c..481cfc06d910441c07ef44ffdf2caee8a8f02a4c 100644 (file)
  */
 package org.openhab.binding.netatmo.internal;
 
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent.EventTypeEnum;
-import org.openhab.core.thing.ThingTypeUID;
 
 /**
- * The {@link NetatmoBinding} class defines common constants, which are used
+ * The {@link NetatmoBindingConstants} class defines common constants, which are used
  * across the whole binding.
  *
  * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
  *
  */
 @NonNullByDefault
 public class NetatmoBindingConstants {
 
-    private static final String BINDING_ID = "netatmo";
-
+    public static final String BINDING_ID = "netatmo";
     public static final String VENDOR = "Netatmo";
 
     // Configuration keys
     public static final String EQUIPMENT_ID = "id";
-    public static final String PARENT_ID = "parentId";
-    public static final String REFRESH_INTERVAL = "refreshInterval";
-    public static final String SETPOINT_DEFAULT_DURATION = "setpointDefaultDuration";
-
-    public static final String WEBHOOK_APP = "app_security";
-
-    // Scale for Weather Station /getmeasure
-    public static final String THIRTY_MINUTES = "30min";
-    public static final String ONE_HOUR = "1hour";
-    public static final String THREE_HOURS = "3hours";
-    public static final String ONE_DAY = "1day";
-    public static final String ONE_WEEK = "1week";
-    public static final String ONE_MONTH = "1month";
-
-    // Type for Weather Station /getmeasure
-    public static final String DATE_MIN_CO2 = "date_min_co2";
-    public static final String DATE_MAX_CO2 = "date_max_co2";
-    public static final String DATE_MIN_HUM = "date_min_hum";
-    public static final String DATE_MAX_HUM = "date_max_hum";
-    public static final String DATE_MIN_NOISE = "date_min_noise";
-    public static final String DATE_MAX_NOISE = "date_max_noise";
-    public static final String DATE_MIN_PRESSURE = "date_min_pressure";
-    public static final String DATE_MAX_PRESSURE = "date_max_pressure";
-    public static final String DATE_MIN_TEMP = "date_min_temp";
-    public static final String DATE_MAX_TEMP = "date_max_temp";
-    public static final String MIN_CO2 = "min_co2";
-    public static final String MAX_CO2 = "max_co2";
-    public static final String MIN_HUM = "min_hum";
-    public static final String MAX_HUM = "max_hum";
-    public static final String MIN_NOISE = "min_noise";
-    public static final String MAX_NOISE = "max_noise";
-    public static final String MIN_PRESSURE = "min_pressure";
-    public static final String MAX_PRESSURE = "max_pressure";
-    public static final String MIN_TEMP = "min_temp";
-    public static final String MAX_TEMP = "max_temp";
-    public static final String SUM_RAIN = "sum_rain";
-
-    // List of Bridge Type UIDs
-    public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "netatmoapi");
-
-    // List of Weather Station Things Type UIDs
-    public static final ThingTypeUID MAIN_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAMain");
-    public static final ThingTypeUID MODULE1_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule1");
-    public static final ThingTypeUID MODULE2_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule2");
-    public static final ThingTypeUID MODULE3_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule3");
-    public static final ThingTypeUID MODULE4_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule4");
-
-    // Netatmo Health Coach
-    public static final ThingTypeUID HOMECOACH_THING_TYPE = new ThingTypeUID(BINDING_ID, "NHC");
-
-    // List of Thermostat Things Type UIDs
-    public static final ThingTypeUID PLUG_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAPlug");
-    public static final ThingTypeUID THERM1_THING_TYPE = new ThingTypeUID(BINDING_ID, "NATherm1");
-
-    // List of Welcome Home Things Type UIDs
-    public static final ThingTypeUID WELCOME_HOME_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAWelcomeHome");
-    public static final ThingTypeUID WELCOME_CAMERA_THING_TYPE = new ThingTypeUID(BINDING_ID, "NACamera");
-    public static final ThingTypeUID WELCOME_PERSON_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAWelcomePerson");
-    // Presence camera
-    public static final ThingTypeUID PRESENCE_CAMERA_THING_TYPE = new ThingTypeUID(BINDING_ID, "NOC");
-
-    // Weather Station Channel ids
-    public static final String CHANNEL_TEMPERATURE = "Temperature";
-    public static final String CHANNEL_TEMP_TREND = "TempTrend";
-    public static final String CHANNEL_HUMIDITY = "Humidity";
-    public static final String CHANNEL_MAX_HUMIDITY = "MaxHumidity";
-    public static final String CHANNEL_MAX_HUMIDITY_THIS_WEEK = "MaxHumidityThisWeek";
-    public static final String CHANNEL_MAX_HUMIDITY_THIS_MONTH = "MaxHumidityThisMonth";
-    public static final String CHANNEL_MIN_HUMIDITY = "MinHumidity";
-    public static final String CHANNEL_MIN_HUMIDITY_THIS_WEEK = "MinHumidityThisWeek";
-    public static final String CHANNEL_MIN_HUMIDITY_THIS_MONTH = "MinHumidityThisMonth";
-    public static final String CHANNEL_HUMIDEX = "Humidex";
-    public static final String CHANNEL_TIMEUTC = "TimeStamp";
-    public static final String CHANNEL_DEWPOINT = "Dewpoint";
-    public static final String CHANNEL_DEWPOINTDEP = "DewpointDepression";
-    public static final String CHANNEL_HEATINDEX = "HeatIndex";
-    public static final String CHANNEL_LAST_STATUS_STORE = "LastStatusStore";
-    public static final String CHANNEL_LAST_MESSAGE = "LastMessage";
-    public static final String CHANNEL_LOCATION = "Location";
-    public static final String CHANNEL_DATE_MAX_CO2 = "DateMaxCo2";
-    public static final String CHANNEL_DATE_MAX_CO2_THIS_WEEK = "DateMaxCo2ThisWeek";
-    public static final String CHANNEL_DATE_MAX_CO2_THIS_MONTH = "DateMaxCo2ThisMonth";
-    public static final String CHANNEL_DATE_MIN_CO2 = "DateMinCo2";
-    public static final String CHANNEL_DATE_MIN_CO2_THIS_WEEK = "DateMinCo2ThisWeek";
-    public static final String CHANNEL_DATE_MIN_CO2_THIS_MONTH = "DateMinCo2ThisMonth";
-    public static final String CHANNEL_DATE_MAX_HUMIDITY = "DateMaxHumidity";
-    public static final String CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK = "DateMaxHumidityThisWeek";
-    public static final String CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH = "DateMaxHumidityThisMonth";
-    public static final String CHANNEL_DATE_MIN_HUMIDITY = "DateMinHumidity";
-    public static final String CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK = "DateMinHumidityThisWeek";
-    public static final String CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH = "DateMinHumidityThisMonth";
-    public static final String CHANNEL_DATE_MAX_NOISE = "DateMaxNoise";
-    public static final String CHANNEL_DATE_MAX_NOISE_THIS_WEEK = "DateMaxNoiseThisWeek";
-    public static final String CHANNEL_DATE_MAX_NOISE_THIS_MONTH = "DateMaxNoiseThisMonth";
-    public static final String CHANNEL_DATE_MIN_NOISE = "DateMinNoise";
-    public static final String CHANNEL_DATE_MIN_NOISE_THIS_WEEK = "DateMinNoiseThisWeek";
-    public static final String CHANNEL_DATE_MIN_NOISE_THIS_MONTH = "DateMinNoiseThisMonth";
-    public static final String CHANNEL_DATE_MAX_PRESSURE = "DateMaxPressure";
-    public static final String CHANNEL_DATE_MAX_PRESSURE_THIS_WEEK = "DateMaxPressureThisWeek";
-    public static final String CHANNEL_DATE_MAX_PRESSURE_THIS_MONTH = "DateMaxPressureThisMonth";
-    public static final String CHANNEL_DATE_MIN_PRESSURE = "DateMinPressure";
-    public static final String CHANNEL_DATE_MIN_PRESSURE_THIS_WEEK = "DateMinPressureThisWeek";
-    public static final String CHANNEL_DATE_MIN_PRESSURE_THIS_MONTH = "DateMinPressureThisMonth";
-    public static final String CHANNEL_DATE_MAX_TEMP = "DateMaxTemp";
-    public static final String CHANNEL_DATE_MAX_TEMP_THIS_WEEK = "DateMaxTempThisWeek";
-    public static final String CHANNEL_DATE_MAX_TEMP_THIS_MONTH = "DateMaxTempThisMonth";
-    public static final String CHANNEL_DATE_MIN_TEMP = "DateMinTemp";
-    public static final String CHANNEL_DATE_MIN_TEMP_THIS_WEEK = "DateMinTempThisWeek";
-    public static final String CHANNEL_DATE_MIN_TEMP_THIS_MONTH = "DateMinTempThisMonth";
-    public static final String CHANNEL_MAX_TEMP = "MaxTemp";
-    public static final String CHANNEL_MAX_TEMP_THIS_WEEK = "MaxTempThisWeek";
-    public static final String CHANNEL_MAX_TEMP_THIS_MONTH = "MaxTempThisMonth";
-    public static final String CHANNEL_MIN_TEMP = "MinTemp";
-    public static final String CHANNEL_MIN_TEMP_THIS_WEEK = "MinTempThisWeek";
-    public static final String CHANNEL_MIN_TEMP_THIS_MONTH = "MinTempThisMonth";
-    public static final String CHANNEL_ABSOLUTE_PRESSURE = "AbsolutePressure";
-    public static final String CHANNEL_CO2 = "Co2";
-    public static final String CHANNEL_MAX_CO2 = "MaxCo2";
-    public static final String CHANNEL_MAX_CO2_THIS_WEEK = "MaxCo2ThisWeek";
-    public static final String CHANNEL_MAX_CO2_THIS_MONTH = "MaxCo2ThisMonth";
-    public static final String CHANNEL_MIN_CO2 = "MinCo2";
-    public static final String CHANNEL_MIN_CO2_THIS_WEEK = "MinCo2ThisWeek";
-    public static final String CHANNEL_MIN_CO2_THIS_MONTH = "MinCo2ThisMonth";
-    public static final String CHANNEL_NOISE = "Noise";
-    public static final String CHANNEL_MAX_NOISE = "MaxNoise";
-    public static final String CHANNEL_MAX_NOISE_THIS_WEEK = "MaxNoiseThisWeek";
-    public static final String CHANNEL_MAX_NOISE_THIS_MONTH = "MaxNoiseThisMonth";
-    public static final String CHANNEL_MIN_NOISE = "MinNoise";
-    public static final String CHANNEL_MIN_NOISE_THIS_WEEK = "MinNoiseThisWeek";
-    public static final String CHANNEL_MIN_NOISE_THIS_MONTH = "MinNoiseThisMonth";
-    public static final String CHANNEL_PRESSURE = "Pressure";
-    public static final String CHANNEL_MAX_PRESSURE = "MaxPressure";
-    public static final String CHANNEL_MAX_PRESSURE_THIS_WEEK = "MaxPressureThisWeek";
-    public static final String CHANNEL_MAX_PRESSURE_THIS_MONTH = "MaxPressureThisMonth";
-    public static final String CHANNEL_MIN_PRESSURE = "MinPressure";
-    public static final String CHANNEL_MIN_PRESSURE_THIS_WEEK = "MinPressureThisWeek";
-    public static final String CHANNEL_MIN_PRESSURE_THIS_MONTH = "MinPressureThisMonth";
-    public static final String CHANNEL_PRESS_TREND = "PressTrend";
-    public static final String CHANNEL_RAIN = "Rain";
-    public static final String CHANNEL_SUM_RAIN1 = "SumRain1";
-    public static final String CHANNEL_SUM_RAIN24 = "SumRain24";
-    public static final String CHANNEL_SUM_RAIN_THIS_WEEK = "SumRainThisWeek";
-    public static final String CHANNEL_SUM_RAIN_THIS_MONTH = "SumRainThisMonth";
-    public static final String CHANNEL_WIND_ANGLE = "WindAngle";
-    public static final String CHANNEL_WIND_STRENGTH = "WindStrength";
-    public static final String CHANNEL_MAX_WIND_STRENGTH = "MaxWindStrength";
-    public static final String CHANNEL_DATE_MAX_WIND_STRENGTH = "DateMaxWindStrength";
-    public static final String CHANNEL_GUST_ANGLE = "GustAngle";
-    public static final String CHANNEL_GUST_STRENGTH = "GustStrength";
-    public static final String CHANNEL_LOW_BATTERY = "LowBattery";
-    public static final String CHANNEL_BATTERY_LEVEL = "BatteryVP";
-    public static final String CHANNEL_WIFI_STATUS = "WifiStatus";
-    public static final String CHANNEL_RF_STATUS = "RfStatus";
-
-    // Healthy Home Coach specific channel
-    public static final String CHANNEL_HEALTH_INDEX = "HealthIndex";
-
-    // Thermostat specific channels
-    public static final String CHANNEL_SETPOINT_MODE = "SetpointMode";
-    public static final String CHANNEL_SETPOINT_END_TIME = "SetpointEndTime";
-    public static final String CHANNEL_SETPOINT_TEMP = "Sp_Temperature";
-    public static final String CHANNEL_THERM_RELAY = "ThermRelayCmd";
-    public static final String CHANNEL_THERM_ORIENTATION = "ThermOrientation";
-    public static final String CHANNEL_CONNECTED_BOILER = "ConnectedBoiler";
-    public static final String CHANNEL_LAST_PLUG_SEEN = "LastPlugSeen";
-    public static final String CHANNEL_LAST_BILAN = "LastBilan";
-
-    public static final String CHANNEL_PLANNING = "Planning";
-
-    public static final String CHANNEL_SETPOINT_MODE_MANUAL = "manual";
-    public static final String CHANNEL_SETPOINT_MODE_AWAY = "away";
-    public static final String CHANNEL_SETPOINT_MODE_HG = "hg";
-    public static final String CHANNEL_SETPOINT_MODE_OFF = "off";
-    public static final String CHANNEL_SETPOINT_MODE_MAX = "max";
-    public static final String CHANNEL_SETPOINT_MODE_PROGRAM = "program";
-
-    // Module Properties
-    public static final String PROPERTY_SIGNAL_LEVELS = "signalLevels";
-    public static final String PROPERTY_BATTERY_LEVELS = "batteryLevels";
-    public static final String PROPERTY_REFRESH_PERIOD = "refreshPeriod";
-
-    // Welcome Home specific channels
-    public static final String CHANNEL_WELCOME_HOME_CITY = "welcomeHomeCity";
-    public static final String CHANNEL_WELCOME_HOME_COUNTRY = "welcomeHomeCountry";
-    public static final String CHANNEL_WELCOME_HOME_TIMEZONE = "welcomeHomeTimezone";
-    public static final String CHANNEL_WELCOME_HOME_PERSONCOUNT = "welcomeHomePersonCount";
-    public static final String CHANNEL_WELCOME_HOME_UNKNOWNCOUNT = "welcomeHomeUnknownCount";
-
-    public static final String CHANNEL_WELCOME_HOME_EVENT = "welcomeHomeEvent";
-
-    public static final String CHANNEL_CAMERA_EVENT = "cameraEvent";
-
-    public static final String CHANNEL_WELCOME_PERSON_LASTSEEN = "welcomePersonLastSeen";
-    public static final String CHANNEL_WELCOME_PERSON_ATHOME = "welcomePersonAtHome";
-    public static final String CHANNEL_WELCOME_PERSON_AVATAR_URL = "welcomePersonAvatarUrl";
-    public static final String CHANNEL_WELCOME_PERSON_AVATAR = "welcomePersonAvatar";
-    public static final String CHANNEL_WELCOME_PERSON_LASTMESSAGE = "welcomePersonLastEventMessage";
-    public static final String CHANNEL_WELCOME_PERSON_LASTTIME = "welcomePersonLastEventTime";
-    public static final String CHANNEL_WELCOME_PERSON_LASTEVENT = "welcomePersonLastEvent";
-    public static final String CHANNEL_WELCOME_PERSON_LASTEVENT_URL = "welcomePersonLastEventUrl";
-
-    public static final String CHANNEL_WELCOME_CAMERA_STATUS = "welcomeCameraStatus";
-    public static final String CHANNEL_WELCOME_CAMERA_SDSTATUS = "welcomeCameraSdStatus";
-    public static final String CHANNEL_WELCOME_CAMERA_ALIMSTATUS = "welcomeCameraAlimStatus";
-    public static final String CHANNEL_WELCOME_CAMERA_ISLOCAL = "welcomeCameraIsLocal";
-    public static final String CHANNEL_WELCOME_CAMERA_LIVEPICTURE = "welcomeCameraLivePicture";
-    public static final String CHANNEL_WELCOME_CAMERA_LIVEPICTURE_URL = "welcomeCameraLivePictureUrl";
-    public static final String CHANNEL_WELCOME_CAMERA_LIVESTREAM_URL = "welcomeCameraLiveStreamUrl";
-
-    public static final String CHANNEL_WELCOME_EVENT_TYPE = "welcomeEventType";
-    public static final String CHANNEL_WELCOME_EVENT_TIME = "welcomeEventTime";
-    public static final String CHANNEL_WELCOME_EVENT_CAMERAID = "welcomeEventCameraId";
-    public static final String CHANNEL_WELCOME_EVENT_PERSONID = "welcomeEventPersonId";
-    public static final String CHANNEL_WELCOME_EVENT_SNAPSHOT = "welcomeEventSnapshot";
-    public static final String CHANNEL_WELCOME_EVENT_SNAPSHOT_URL = "welcomeEventSnapshotURL";
-    public static final String CHANNEL_WELCOME_EVENT_VIDEO_URL = "welcomeEventVideoURL";
-    public static final String CHANNEL_WELCOME_EVENT_VIDEOSTATUS = "welcomeEventVideoStatus";
-    public static final String CHANNEL_WELCOME_EVENT_ISARRIVAL = "welcomeEventIsArrival";
-    public static final String CHANNEL_WELCOME_EVENT_MESSAGE = "welcomeEventMessage";
-    public static final String CHANNEL_WELCOME_EVENT_SUBTYPE = "welcomeEventSubType";
-
-    // Camera specific channels
-    public static final String CHANNEL_CAMERA_STATUS = "cameraStatus";
-    public static final String CHANNEL_CAMERA_SDSTATUS = "cameraSdStatus";
-    public static final String CHANNEL_CAMERA_ALIMSTATUS = "cameraAlimStatus";
-    public static final String CHANNEL_CAMERA_ISLOCAL = "cameraIsLocal";
-    public static final String CHANNEL_CAMERA_LIVEPICTURE = "cameraLivePicture";
-    public static final String CHANNEL_CAMERA_LIVEPICTURE_URL = "cameraLivePictureUrl";
-    public static final String CHANNEL_CAMERA_LIVESTREAM_URL = "cameraLiveStreamUrl";
-
-    public static final String WELCOME_PICTURE_URL = "https://api.netatmo.com/api/getcamerapicture";
-    public static final String WELCOME_PICTURE_IMAGEID = "image_id";
-    public static final String WELCOME_PICTURE_KEY = "key";
-
-    // Presence outdoor camera specific channels
-    public static final String CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE = "cameraFloodlightAutoMode";
-    public static final String CHANNEL_CAMERA_FLOODLIGHT = "cameraFloodlight";
-
-    // List of all supported physical devices and modules
-    public static final Set<ThingTypeUID> SUPPORTED_DEVICE_THING_TYPES_UIDS = Stream
-            .of(MAIN_THING_TYPE, MODULE1_THING_TYPE, MODULE2_THING_TYPE, MODULE3_THING_TYPE, MODULE4_THING_TYPE,
-                    HOMECOACH_THING_TYPE, PLUG_THING_TYPE, THERM1_THING_TYPE, WELCOME_HOME_THING_TYPE,
-                    WELCOME_CAMERA_THING_TYPE, WELCOME_PERSON_THING_TYPE, PRESENCE_CAMERA_THING_TYPE)
-            .collect(Collectors.toSet());
-
-    // List of all adressable things in OH = SUPPORTED_DEVICE_THING_TYPES_UIDS + the virtual bridge
-    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
-            .concat(SUPPORTED_DEVICE_THING_TYPES_UIDS.stream(), Stream.of(APIBRIDGE_THING_TYPE))
-            .collect(Collectors.toSet());
 
-    public static final Set<EventTypeEnum> HOME_EVENTS = Stream.of(EventTypeEnum.PERSON_AWAY)
-            .collect(Collectors.toSet());
-    public static final Set<EventTypeEnum> WELCOME_EVENTS = Stream
-            .of(EventTypeEnum.PERSON, EventTypeEnum.MOVEMENT, EventTypeEnum.CONNECTION, EventTypeEnum.DISCONNECTION,
-                    EventTypeEnum.ON, EventTypeEnum.OFF, EventTypeEnum.BOOT, EventTypeEnum.SD, EventTypeEnum.ALIM,
-                    EventTypeEnum.NEW_MODULE, EventTypeEnum.MODULE_CONNECT, EventTypeEnum.MODULE_DISCONNECT,
-                    EventTypeEnum.MODULE_LOW_BATTERY, EventTypeEnum.MODULE_END_UPDATE, EventTypeEnum.TAG_BIG_MOVE,
-                    EventTypeEnum.TAG_SMALL_MOVE, EventTypeEnum.TAG_UNINSTALLED, EventTypeEnum.TAG_OPEN)
-            .collect(Collectors.toSet());
-    public static final Set<EventTypeEnum> PERSON_EVENTS = Stream.of(EventTypeEnum.PERSON, EventTypeEnum.PERSON_AWAY)
-            .collect(Collectors.toSet());
-    public static final Set<EventTypeEnum> PRESENCE_EVENTS = Stream
-            .of(EventTypeEnum.OUTDOOR, EventTypeEnum.ALIM, EventTypeEnum.DAILY_SUMMARY).collect(Collectors.toSet());
+    // Things properties
+    public static final String PROPERTY_CITY = "city";
+    public static final String PROPERTY_COUNTRY = "country";
+    public static final String PROPERTY_TIMEZONE = "timezone";
+    public static final String PROPERTY_FEATURE = "feature";
+
+    // Channel group ids
+    public static final String GROUP_LAST_EVENT = "last-event";
+    public static final String GROUP_TEMPERATURE = "temperature";
+    public static final String GROUP_HUMIDITY = "humidity";
+    public static final String GROUP_AIR_QUALITY = "airquality";
+    public static final String GROUP_NOISE = "noise";
+    public static final String GROUP_PRESSURE = "pressure";
+    public static final String GROUP_TIMESTAMP = "timestamp";
+    public static final String GROUP_RAIN = "rain";
+    public static final String GROUP_WIND = "wind";
+    public static final String GROUP_ENERGY = "energy";
+    public static final String GROUP_SIGNAL = "signal";
+    public static final String GROUP_BATTERY = "battery";
+    public static final String GROUP_SECURITY = "security";
+    public static final String GROUP_CAM_STATUS = "status";
+    public static final String GROUP_CAM_LIVE = "live";
+    public static final String GROUP_PRESENCE = "presence";
+    public static final String GROUP_PERSON = "person";
+    public static final String GROUP_PERSON_EVENT = "person-event";
+    public static final String GROUP_ROOM_TEMPERATURE = "room-temperature";
+    public static final String GROUP_ROOM_PROPERTIES = "room-properties";
+    public static final String GROUP_TH_PROPERTIES = "th-properties";
+    public static final String GROUP_TH_SETPOINT = "setpoint";
+    public static final String GROUP_LOCATION = "location";
+
+    // Alternative extended groups
+    public static final String OPTION_EXTENDED = "-extended";
+    public static final String OPTION_OUTSIDE = "-outside";
+    public static final String GROUP_TYPE_TIMESTAMP_EXTENDED = GROUP_TIMESTAMP + OPTION_EXTENDED;
+    public static final String GROUP_TYPE_BATTERY_EXTENDED = GROUP_BATTERY + OPTION_EXTENDED;
+    public static final String GROUP_TYPE_PRESSURE_EXTENDED = GROUP_PRESSURE + OPTION_EXTENDED;
+    public static final String GROUP_TYPE_TEMPERATURE_EXTENDED = GROUP_TEMPERATURE + OPTION_EXTENDED;
+    public static final String GROUP_TYPE_AIR_QUALITY_EXTENDED = GROUP_AIR_QUALITY + OPTION_EXTENDED;
+    public static final String GROUP_TYPE_TEMPERATURE_OUTSIDE = GROUP_TEMPERATURE + OPTION_OUTSIDE;
+
+    // Channel ids
+    public static final String CHANNEL_VALUE = "value";
+    public static final String CHANNEL_TREND = "trend";
+    public static final String CHANNEL_MAX_TIME = "max-time";
+    public static final String CHANNEL_MIN_TIME = "min-time";
+    public static final String CHANNEL_MAX_VALUE = "max-today";
+    public static final String CHANNEL_MIN_VALUE = "min-today";
+    public static final String CHANNEL_HUMIDEX = "humidex";
+    public static final String CHANNEL_CO2 = "co2";
+    public static final String CHANNEL_HEALTH_INDEX = "health-index";
+    public static final String CHANNEL_HUMIDEX_SCALE = "humidex-scale";
+    public static final String CHANNEL_DEWPOINT = "dewpoint";
+    public static final String CHANNEL_DEWPOINT_DEP = "dewpoint-depression";
+    public static final String CHANNEL_HEAT_INDEX = "heat-index";
+    public static final String CHANNEL_ABSOLUTE_PRESSURE = "absolute";
+    public static final String CHANNEL_LAST_SEEN = "last-seen";
+    public static final String CHANNEL_MEASURES_TIMESTAMP = "measures";
+    public static final String CHANNEL_LOW_BATTERY = "low-battery";
+    public static final String CHANNEL_BATTERY_STATUS = "status";
+    public static final String CHANNEL_SIGNAL_STRENGTH = "strength";
+    public static final String CHANNEL_SUM_RAIN1 = "sum-1";
+    public static final String CHANNEL_SUM_RAIN24 = "sum-24";
+    public static final String CHANNEL_WIND_ANGLE = "angle";
+    public static final String CHANNEL_WIND_STRENGTH = "strength";
+    public static final String CHANNEL_MAX_WIND_STRENGTH = "max-strength";
+    public static final String CHANNEL_DATE_MAX_WIND_STRENGTH = "max-strength-date";
+    public static final String CHANNEL_GUST_ANGLE = "gust-angle";
+    public static final String CHANNEL_GUST_STRENGTH = "gust-strength";
+    public static final String CHANNEL_SETPOINT_MODE = "mode";
+    public static final String CHANNEL_SETPOINT_START_TIME = "start";
+    public static final String CHANNEL_SETPOINT_END_TIME = "end";
+    public static final String CHANNEL_THERM_RELAY = "relay-status";
+    public static final String CHANNEL_ANTICIPATING = "anticipating";
+    public static final String CHANNEL_ROOM_WINDOW_OPEN = "window-open";
+    public static final String CHANNEL_ROOM_HEATING_POWER = "heating-power-request";
+    public static final String CHANNEL_PLANNING = "planning";
+    public static final String CHANNEL_PERSON_COUNT = "person-count";
+    public static final String CHANNEL_UNKNOWN_PERSON_COUNT = "unknown-person-count";
+    public static final String CHANNEL_UNKNOWN_PERSON_PICTURE = "unknown-person-picture";
+    public static final String CHANNEL_MONITORING = "monitoring";
+    public static final String CHANNEL_SD_CARD = "sd-card";
+    public static final String CHANNEL_ALIM_STATUS = "alim";
+    public static final String CHANNEL_LIVEPICTURE = "picture";
+    public static final String CHANNEL_LIVEPICTURE_VPN_URL = "vpn-picture-url";
+    public static final String CHANNEL_LIVEPICTURE_LOCAL_URL = "local-picture-url";
+    public static final String CHANNEL_LIVESTREAM_VPN_URL = "vpn-stream-url";
+    public static final String CHANNEL_LIVESTREAM_LOCAL_URL = "local-stream-url";
+    public static final String CHANNEL_EVENT_TYPE = "type";
+    public static final String CHANNEL_EVENT_SUBTYPE = "subtype";
+    public static final String CHANNEL_EVENT_VIDEO_STATUS = "video-status";
+    public static final String CHANNEL_EVENT_MESSAGE = "message";
+    public static final String CHANNEL_EVENT_TIME = "time";
+    public static final String CHANNEL_EVENT_SNAPSHOT = "snapshot";
+    public static final String CHANNEL_EVENT_SNAPSHOT_URL = "snapshot-url";
+    public static final String CHANNEL_EVENT_VIDEO_VPN_URL = "vpn-video-url";
+    public static final String CHANNEL_EVENT_VIDEO_LOCAL_URL = "local-video-url";
+    public static final String CHANNEL_EVENT_PERSON_ID = "person-id";
+    public static final String CHANNEL_EVENT_CAMERA_ID = "camera-id";
+    public static final String CHANNEL_PERSON_AT_HOME = "at-home";
+    public static final String CHANNEL_PERSON_AVATAR = "avatar";
+    public static final String CHANNEL_PERSON_AVATAR_URL = "avatar-url";
+    public static final String CHANNEL_HOME_EVENT = "home-event";
+    public static final String CHANNEL_SETPOINT_DURATION = "setpoint-duration";
+    public static final String CHANNEL_FLOODLIGHT = "floodlight";
 }
index 9df2de1e3e69daa72428c632757500f2ab720940..0ab898bbc95d9c2317550bf06db7c8770d06f4f4 100644 (file)
  */
 package org.openhab.binding.netatmo.internal;
 
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
-import javax.servlet.http.HttpServlet;
-
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.discovery.NetatmoModuleDiscoveryService;
-import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
-import org.openhab.binding.netatmo.internal.homecoach.NAHealthyHomeCoachHandler;
-import org.openhab.binding.netatmo.internal.presence.NAPresenceCameraHandler;
-import org.openhab.binding.netatmo.internal.station.NAMainHandler;
-import org.openhab.binding.netatmo.internal.station.NAModule1Handler;
-import org.openhab.binding.netatmo.internal.station.NAModule2Handler;
-import org.openhab.binding.netatmo.internal.station.NAModule3Handler;
-import org.openhab.binding.netatmo.internal.station.NAModule4Handler;
-import org.openhab.binding.netatmo.internal.thermostat.NAPlugHandler;
-import org.openhab.binding.netatmo.internal.thermostat.NATherm1Handler;
-import org.openhab.binding.netatmo.internal.webhook.WelcomeWebHookServlet;
-import org.openhab.binding.netatmo.internal.welcome.NAWelcomeCameraHandler;
-import org.openhab.binding.netatmo.internal.welcome.NAWelcomeHomeHandler;
-import org.openhab.binding.netatmo.internal.welcome.NAWelcomePersonHandler;
-import org.openhab.core.config.discovery.DiscoveryService;
-import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.i18n.TranslationProvider;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.config.BindingConfiguration;
+import org.openhab.binding.netatmo.internal.deserialization.NADeserializer;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.DeviceHandler;
+import org.openhab.binding.netatmo.internal.handler.ModuleHandler;
+import org.openhab.binding.netatmo.internal.handler.capability.AirCareCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.CameraCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.Capability;
+import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
+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.PersonCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability;
+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;
+import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+import org.openhab.core.config.core.ConfigParser;
+import org.openhab.core.io.net.http.HttpClientFactory;
 import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.BaseThingHandler;
 import org.openhab.core.thing.binding.BaseThingHandlerFactory;
 import org.openhab.core.thing.binding.ThingHandler;
 import org.openhab.core.thing.binding.ThingHandlerFactory;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.component.ComponentContext;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.http.HttpService;
 import org.slf4j.Logger;
@@ -68,131 +67,91 @@ import org.slf4j.LoggerFactory;
 @Component(service = ThingHandlerFactory.class, configurationPid = "binding.netatmo")
 public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
     private final Logger logger = LoggerFactory.getLogger(NetatmoHandlerFactory.class);
-    private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
-    private final Map<ThingUID, ServiceRegistration<?>> webHookServiceRegs = new HashMap<>();
+
+    private final NetatmoDescriptionProvider stateDescriptionProvider;
+    private final HttpClient httpClient;
+    private final NADeserializer deserializer;
     private final HttpService httpService;
-    private final NATherm1StateDescriptionProvider stateDescriptionProvider;
-    private final TimeZoneProvider timeZoneProvider;
-    private final LocaleProvider localeProvider;
-    private final TranslationProvider translationProvider;
-    private boolean backgroundDiscovery;
+    private final BindingConfiguration configuration = new BindingConfiguration();
 
     @Activate
-    public NetatmoHandlerFactory(final @Reference HttpService httpService,
-            final @Reference NATherm1StateDescriptionProvider stateDescriptionProvider,
-            final @Reference TimeZoneProvider timeZoneProvider, final @Reference LocaleProvider localeProvider,
-            final @Reference TranslationProvider translationProvider) {
-        this.httpService = httpService;
+    public NetatmoHandlerFactory(@Reference NetatmoDescriptionProvider stateDescriptionProvider,
+            @Reference HttpClientFactory factory, @Reference NADeserializer deserializer,
+            @Reference HttpService httpService, Map<String, @Nullable Object> config) {
         this.stateDescriptionProvider = stateDescriptionProvider;
-        this.timeZoneProvider = timeZoneProvider;
-        this.localeProvider = localeProvider;
-        this.translationProvider = translationProvider;
+        this.httpClient = factory.getCommonHttpClient();
+        this.httpService = httpService;
+        this.deserializer = deserializer;
+        configChanged(config);
     }
 
-    @Override
-    protected void activate(ComponentContext componentContext) {
-        super.activate(componentContext);
-        Dictionary<String, Object> properties = componentContext.getProperties();
-        Object property = properties.get("backgroundDiscovery");
-        if (property instanceof Boolean) {
-            backgroundDiscovery = ((Boolean) property).booleanValue();
-        } else {
-            backgroundDiscovery = false;
+    @Modified
+    public void configChanged(Map<String, @Nullable Object> config) {
+        BindingConfiguration newConf = ConfigParser.configurationAs(config, BindingConfiguration.class);
+        if (newConf != null) {
+            configuration.update(newConf);
         }
-        logger.debug("backgroundDiscovery {}", backgroundDiscovery);
     }
 
     @Override
     public boolean supportsThingType(ThingTypeUID thingTypeUID) {
-        return (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID));
+        return ModuleType.AS_SET.stream().anyMatch(mt -> mt.thingTypeUID.equals(thingTypeUID));
     }
 
     @Override
     protected @Nullable ThingHandler createHandler(Thing thing) {
         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
-        if (thingTypeUID.equals(APIBRIDGE_THING_TYPE)) {
-            WelcomeWebHookServlet servlet = registerWebHookServlet(thing.getUID());
-            NetatmoBridgeHandler bridgeHandler = new NetatmoBridgeHandler((Bridge) thing, servlet);
-            registerDeviceDiscoveryService(bridgeHandler);
-            return bridgeHandler;
-        } else if (thingTypeUID.equals(MODULE1_THING_TYPE)) {
-            return new NAModule1Handler(thing, timeZoneProvider);
-        } else if (thingTypeUID.equals(MODULE2_THING_TYPE)) {
-            return new NAModule2Handler(thing, timeZoneProvider);
-        } else if (thingTypeUID.equals(MODULE3_THING_TYPE)) {
-            return new NAModule3Handler(thing, timeZoneProvider);
-        } else if (thingTypeUID.equals(MODULE4_THING_TYPE)) {
-            return new NAModule4Handler(thing, timeZoneProvider);
-        } else if (thingTypeUID.equals(MAIN_THING_TYPE)) {
-            return new NAMainHandler(thing, timeZoneProvider);
-        } else if (thingTypeUID.equals(HOMECOACH_THING_TYPE)) {
-            return new NAHealthyHomeCoachHandler(thing, timeZoneProvider);
-        } else if (thingTypeUID.equals(PLUG_THING_TYPE)) {
-            return new NAPlugHandler(thing, timeZoneProvider);
-        } else if (thingTypeUID.equals(THERM1_THING_TYPE)) {
-            return new NATherm1Handler(thing, stateDescriptionProvider, timeZoneProvider);
-        } else if (thingTypeUID.equals(WELCOME_HOME_THING_TYPE)) {
-            return new NAWelcomeHomeHandler(thing, timeZoneProvider);
-        } else if (thingTypeUID.equals(WELCOME_CAMERA_THING_TYPE)) {
-            return new NAWelcomeCameraHandler(thing, timeZoneProvider);
-        } else if (thingTypeUID.equals(PRESENCE_CAMERA_THING_TYPE)) {
-            return new NAPresenceCameraHandler(thing, timeZoneProvider);
-        } else if (thingTypeUID.equals(WELCOME_PERSON_THING_TYPE)) {
-            return new NAWelcomePersonHandler(thing, timeZoneProvider);
-        } else {
-            logger.warn("ThingHandler not found for {}", thing.getThingTypeUID());
-            return null;
-        }
+        return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst()
+                .map(mt -> buildHandler(thing, mt)).orElse(null);
     }
 
-    @Override
-    protected void removeHandler(ThingHandler thingHandler) {
-        if (thingHandler instanceof NetatmoBridgeHandler) {
-            ThingUID thingUID = thingHandler.getThing().getUID();
-            unregisterDeviceDiscoveryService(thingUID);
-            unregisterWebHookServlet(thingUID);
+    private BaseThingHandler buildHandler(Thing thing, ModuleType moduleType) {
+        if (ModuleType.ACCOUNT.equals(moduleType)) {
+            return new ApiBridgeHandler((Bridge) thing, httpClient, httpService, deserializer, configuration);
         }
-    }
+        CommonInterface handler = moduleType.isABridge() ? new DeviceHandler((Bridge) thing) : new ModuleHandler(thing);
 
-    private synchronized void registerDeviceDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler) {
-        if (bundleContext != null) {
-            NetatmoModuleDiscoveryService discoveryService = new NetatmoModuleDiscoveryService(netatmoBridgeHandler,
-                    localeProvider, translationProvider);
-            Map<String, Object> configProperties = new HashMap<>();
-            configProperties.put(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY,
-                    Boolean.valueOf(backgroundDiscovery));
-            discoveryService.activate(configProperties);
-            discoveryServiceRegs.put(netatmoBridgeHandler.getThing().getUID(), bundleContext
-                    .registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
-        }
-    }
-
-    private synchronized void unregisterDeviceDiscoveryService(ThingUID thingUID) {
-        ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(thingUID);
-        if (serviceReg != null) {
-            NetatmoModuleDiscoveryService service = (NetatmoModuleDiscoveryService) bundleContext
-                    .getService(serviceReg.getReference());
-            serviceReg.unregister();
-            if (service != null) {
-                service.deactivate();
+        List<ChannelHelper> helpers = new ArrayList<>();
+        moduleType.channelHelpers.forEach(helperClass -> {
+            try {
+                helpers.add(helperClass.getConstructor().newInstance());
+            } catch (ReflectiveOperationException e) {
+                logger.warn("Error creating or initializing helper class : {}", e.getMessage());
             }
-        }
-    }
+        });
 
-    private synchronized @Nullable WelcomeWebHookServlet registerWebHookServlet(ThingUID thingUID) {
-        WelcomeWebHookServlet servlet = null;
-        if (bundleContext != null) {
-            servlet = new WelcomeWebHookServlet(httpService, thingUID.getId());
-            webHookServiceRegs.put(thingUID,
-                    bundleContext.registerService(HttpServlet.class.getName(), servlet, new Hashtable<>()));
-        }
-        return servlet;
-    }
+        moduleType.capabilities.forEach(capability -> {
+            Capability newCap = null;
+            if (capability == DeviceCapability.class) {
+                newCap = new DeviceCapability(handler);
+            } else if (capability == AirCareCapability.class) {
+                newCap = new AirCareCapability(handler);
+            } else if (capability == EventCapability.class) {
+                newCap = new EventCapability(handler);
+            } else if (capability == HomeCapability.class) {
+                newCap = new HomeCapability(handler, stateDescriptionProvider);
+            } else if (capability == WeatherCapability.class) {
+                newCap = new WeatherCapability(handler);
+            } else if (capability == RoomCapability.class) {
+                newCap = new RoomCapability(handler);
+            } else if (capability == PersonCapability.class) {
+                newCap = new PersonCapability(handler, stateDescriptionProvider, helpers);
+            } else if (capability == CameraCapability.class) {
+                newCap = new CameraCapability(handler, stateDescriptionProvider, helpers);
+            } else if (capability == PresenceCapability.class) {
+                newCap = new PresenceCapability(handler, stateDescriptionProvider, helpers);
+            } else if (capability == MeasureCapability.class) {
+                newCap = new MeasureCapability(handler, helpers);
+            } else if (capability == ChannelHelperCapability.class) {
+                newCap = new ChannelHelperCapability(handler, helpers);
+            }
+            if (newCap != null) {
+                handler.getCapabilities().put(newCap);
+            } else {
+                logger.warn("No factory entry defined to create Capability : {}", capability);
+            }
+        });
 
-    private synchronized void unregisterWebHookServlet(ThingUID thingUID) {
-        ServiceRegistration<?> serviceReg = webHookServiceRegs.remove(thingUID);
-        if (serviceReg != null) {
-            serviceReg.unregister();
-        }
+        return (BaseThingHandler) handler;
     }
 }
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/RefreshStrategy.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/RefreshStrategy.java
deleted file mode 100644 (file)
index 5cff434..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal;
-
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.time.temporal.ChronoUnit;
-import java.util.Calendar;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * {@link RefreshStrategy} is the class used to embed the refreshing
- * needs calculation for devices
- *
- * @author Gaël L'hopital - Initial contribution
- *
- */
-@NonNullByDefault
-public class RefreshStrategy {
-
-    private Logger logger = LoggerFactory.getLogger(RefreshStrategy.class);
-
-    private static final int DEFAULT_DELAY = 30; // in seconds
-    private static final int SEARCH_REFRESH_INTERVAL = 120; // in seconds
-    private int dataValidityPeriod;
-    private long dataTimeStamp;
-    private boolean searchRefreshInterval;
-    @Nullable
-    private Integer dataTimestamp0;
-
-    // By default we create dataTimeStamp to be outdated
-    // A null or negative value for dataValidityPeriod will trigger an automatic search of the validity period
-    public RefreshStrategy(int dataValidityPeriod) {
-        if (dataValidityPeriod <= 0) {
-            this.dataValidityPeriod = 0;
-            this.searchRefreshInterval = true;
-            logger.debug("Data validity period search...");
-        } else {
-            this.dataValidityPeriod = dataValidityPeriod;
-            this.searchRefreshInterval = false;
-            logger.debug("Data validity period set to {} ms", this.dataValidityPeriod);
-        }
-        expireData();
-    }
-
-    @SuppressWarnings("null")
-    public void setDataTimeStamp(Integer dataTimestamp, ZoneId zoneId) {
-        if (searchRefreshInterval) {
-            if (dataTimestamp0 == null) {
-                dataTimestamp0 = dataTimestamp;
-                logger.debug("First data timestamp is {}", dataTimestamp0);
-            } else if (dataTimestamp.intValue() > dataTimestamp0.intValue()) {
-                dataValidityPeriod = (dataTimestamp.intValue() - dataTimestamp0.intValue()) * 1000;
-                searchRefreshInterval = false;
-                logger.debug("Data validity period found : {} ms", this.dataValidityPeriod);
-            } else {
-                logger.debug("Data validity period not yet found - data timestamp unchanged");
-            }
-        }
-        this.dataTimeStamp = ChannelTypeUtils.toZonedDateTime(dataTimestamp, zoneId).toInstant().toEpochMilli();
-    }
-
-    public long dataAge() {
-        long now = Calendar.getInstance().getTimeInMillis();
-        return now - dataTimeStamp;
-    }
-
-    public boolean isDataOutdated() {
-        return dataAge() >= dataValidityPeriod;
-    }
-
-    public long nextRunDelayInS() {
-        return searchRefreshInterval ? SEARCH_REFRESH_INTERVAL
-                : Math.max(0, (dataValidityPeriod - dataAge())) / 1000 + DEFAULT_DELAY;
-    }
-
-    public void expireData() {
-        ZonedDateTime now = ZonedDateTime.now().minus(this.dataValidityPeriod, ChronoUnit.MILLIS);
-        dataTimeStamp = now.toInstant().toEpochMilli();
-    }
-
-    public boolean isSearchingRefreshInterval() {
-        return searchRefreshInterval && dataTimestamp0 != null;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/WeatherUtils.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/WeatherUtils.java
deleted file mode 100644 (file)
index 42376f9..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * This class holds various unit/measurement conversion methods
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - updated heat index
- */
-@NonNullByDefault
-public class WeatherUtils {
-
-    /**
-     * Calculate the heat index using temperature and humidity
-     * https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
-     *
-     * @param temperature in (°C)
-     * @param humidity relative level (%)
-     * @return heatIndex in (°C)
-     */
-    public static double getHeatIndex(double temperature, double humidity) {
-        double tempF = (temperature * 9.0 / 5.0) + 32.0; // calculations are done in Fahrenheit
-        double heatIndex;
-        if (tempF >= 80.0) {
-            heatIndex = -42.379 + (2.04901523 * tempF) + (10.14333127 * humidity) - (0.22475541 * tempF * humidity)
-                    - (0.00683783 * tempF * tempF) - (0.05481717 * humidity * humidity)
-                    + (0.00122874 * tempF * tempF * humidity) + (0.00085282 * tempF * humidity * humidity)
-                    - (0.00000199 * tempF * tempF * humidity * humidity);
-            if (humidity < 13.0 && tempF <= 112.0) {
-                heatIndex -= ((13.0 - humidity) / 4.0) * Math.sqrt((17.0 - Math.abs(tempF - 95.0)) / 17.0);
-            } else if (humidity > 85.0 && tempF <= 87.0) {
-                heatIndex += ((humidity - 85.0) / 10.0) * ((87.0 - tempF) / 5.0);
-            }
-        } else {
-            heatIndex = 0.5 * (tempF + 61.0 + ((tempF - 68.0) * 1.2) + (humidity * 0.094));
-        }
-
-        return (heatIndex - 32) * 5.0 / 9.0; // convert back to Celsius
-    }
-
-    public static double getDewPointDep(double temperature, double dewpoint) {
-        return temperature - dewpoint;
-    }
-
-    /**
-     * Compute the Dewpoint temperature given temperature and hygrometry
-     * valid up to 60 degrees, from
-     * http://en.wikipedia.org/wiki/Dew_point#Calculating_the_dew_point
-     *
-     * @param temperature in (°C)
-     * @param humidity relative level (%)
-     * @return dewpoint temperature
-     */
-    public static double getDewPoint(double temperature, double humidity) {
-        double a = 17.271, b = 237.2;
-        double gamma = ((a * temperature) / (b + temperature)) + Math.log(humidity / 100.0);
-        return b * gamma / (a - gamma);
-    }
-
-    /**
-     * Compute the Humidex index given temperature and hygrometry
-     *
-     *
-     * @param temperature in (°C)
-     * @param hygro relative level (%)
-     * @return Humidex index value
-     */
-    public static double getHumidex(double temperature, double hygro) {
-        double result = 6.112 * Math.pow(10, 7.5 * temperature / (237.7 + temperature)) * hygro / 100;
-        result = temperature + 0.555555556 * (result - 10);
-        return result;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/action/RoomActions.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/action/RoomActions.java
new file mode 100644 (file)
index 0000000..1927356
--- /dev/null
@@ -0,0 +1,150 @@
+/**
+ * Copyright (c) 2010-2022 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.action;
+
+import java.util.Optional;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.capability.EnergyCapability;
+import org.openhab.core.automation.annotation.ActionInput;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link RoomActions} defines thing actions for RoomHandler.
+ *
+ * @author Markus Dillmann - Initial contribution
+ */
+@ThingActionsScope(name = "netatmo")
+@NonNullByDefault
+public class RoomActions implements ThingActions {
+    private final Logger logger = LoggerFactory.getLogger(RoomActions.class);
+    private static final Set<SetpointMode> ALLOWED_MODES = Set.of(SetpointMode.MAX, SetpointMode.MANUAL,
+            SetpointMode.HOME);
+
+    private @Nullable CommonInterface handler;
+    private Optional<EnergyCapability> energy = Optional.empty();
+
+    public RoomActions() {
+        logger.debug("Netatmo RoomActions service created");
+    }
+
+    @Override
+    public void setThingHandler(@Nullable ThingHandler handler) {
+        if (handler instanceof CommonInterface) {
+            CommonInterface commonHandler = (CommonInterface) handler;
+            this.handler = commonHandler;
+            energy = commonHandler.getHomeCapability(EnergyCapability.class);
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return (ThingHandler) handler;
+    }
+
+    /**
+     * The setThermpoint room thing action
+     */
+    @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
+    public void setThermpoint(
+            @ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp,
+            @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) {
+        setThermpoint(temp, endTime, "MANUAL");
+    }
+
+    @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
+    public void seThermpoint(
+            @ActionInput(name = "mode", label = "@text/actionInputModeLabel", description = "@text/actionInputModeDesc") @Nullable String mode,
+            @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) {
+        setThermpoint(null, endTime, mode);
+    }
+
+    @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
+    public void setThermpoint(
+            @ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp,
+            @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime,
+            @ActionInput(name = "mode", label = "@text/actionInputModeLabel", description = "@text/actionInputModeDesc") @Nullable String mode) {
+        CommonInterface roomHandler = handler;
+        if (roomHandler != null) {
+            String roomId = roomHandler.getId();
+            SetpointMode targetMode = SetpointMode.UNKNOWN;
+            Long targetEndTime = endTime;
+            Double targetTemp = temp;
+            if (mode != null) {
+                try {
+                    targetMode = SetpointMode.valueOf(mode);
+                    if (!ALLOWED_MODES.contains(targetMode)) {
+                        logger.info("Mode can only be MAX, HOME or MANUAL for a room");
+                        return;
+                    }
+                } catch (IllegalArgumentException e) {
+                    logger.info("Invalid mode passed : {} - {}", mode, e.getMessage());
+                    return;
+                }
+            }
+            if (temp != null) {
+                logger.debug("Temperature provided, mode forced to MANUAL.");
+                targetMode = SetpointMode.MANUAL;
+                if (targetEndTime == null) {
+                    logger.info("Temperature provided but no endtime given, action ignored");
+                    return;
+                }
+            } else {
+                if (SetpointMode.HOME.equals(targetMode)) {
+                    targetEndTime = 0L;
+                    targetTemp = 0.0;
+                } else {
+                    logger.info("mode is required if no temperature setpoint provided");
+                    return;
+                }
+            }
+
+            try {
+                double setpointTemp = targetTemp != null ? targetTemp : 0;
+                long setpointEnd = targetEndTime;
+                SetpointMode setpointMode = targetMode;
+                energy.ifPresent(cap -> cap.setRoomThermTemp(roomId, setpointTemp, setpointEnd, setpointMode));
+            } catch (IllegalArgumentException e) {
+                logger.debug("Ignoring setRoomThermpoint command due to illegal argument exception: {}",
+                        e.getMessage());
+            }
+        } else {
+            logger.info("Handler not set for room thing actions.");
+        }
+    }
+
+    /**
+     * Static setThermpoint method for Rules DSL backward compatibility
+     */
+    public static void setThermpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime,
+            @Nullable String mode) {
+        ((RoomActions) actions).setThermpoint(temp, endTime, mode);
+    }
+
+    public static void setThermpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime) {
+        setThermpoint(actions, temp, endTime, null);
+    }
+
+    public static void setThermpoint(ThingActions actions, @Nullable String mode, @Nullable Long endTime) {
+        setThermpoint(actions, null, endTime, mode);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AircareApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AircareApi.java
new file mode 100644 (file)
index 0000000..a2d41db
--- /dev/null
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain.StationDataResponse;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * Base class for all Air Care related endpoints
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class AircareApi extends RestManager {
+
+    public AircareApi(ApiBridgeHandler apiClient) {
+        super(apiClient, FeatureArea.AIR_CARE);
+    }
+
+    /**
+     * Returns data from Healthy Home Coach Station (measures and device specific data).
+     *
+     * @param deviceId Id of the device you want to retrieve information of (optional)
+     * @return StationDataResponse
+     * @throws NetatmoException If fail to call the API, e.g. server error or deserializing
+     */
+    public StationDataResponse getHomeCoachData(@Nullable String deviceId) throws NetatmoException {
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMECOACH, PARAM_DEVICEID, deviceId);
+        return get(uriBuilder, StationDataResponse.class);
+    }
+
+    public NAMain getHomeCoach(String deviceId) throws NetatmoException {
+        ListBodyResponse<NAMain> answer = getHomeCoachData(deviceId).getBody();
+        if (answer != null) {
+            NAMain station = answer.getElement(deviceId);
+            if (station != null) {
+                return station;
+            }
+        }
+        throw new NetatmoException("Unexpected answer querying device '%s' : not found.", deviceId);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiError.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiError.java
new file mode 100644 (file)
index 0000000..5901c89
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError;
+
+/**
+ * The {@link ApiError} models an errored response from API
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ApiError {
+    private class Body {
+        private String message = "";
+        private ServiceError code = ServiceError.UNKNOWN;
+    }
+
+    private Body error = new Body();
+
+    public String getMessage() {
+        return error.message;
+    }
+
+    public ServiceError getCode() {
+        return error.code;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiResponse.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiResponse.java
new file mode 100644 (file)
index 0000000..28aab22
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link ApiResponse} models a response returned by API call
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ApiResponse<T> {
+    /**
+     * The {@link Ok} models a response that only holds the result of the request sent to the API
+     */
+    static class Ok extends ApiResponse<String> {
+        private static final String SUCCESS = "ok";
+
+        boolean failed() {
+            return !SUCCESS.equals(getStatus());
+        }
+    }
+
+    private String status = "";
+    private @Nullable T body;
+
+    public String getStatus() {
+        return status;
+    }
+
+    public @Nullable T getBody() {
+        return body;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java
new file mode 100644 (file)
index 0000000..2da5144
--- /dev/null
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.PATH_OAUTH;
+import static org.openhab.core.auth.oauth2client.internal.Keyword.*;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
+import org.openhab.binding.netatmo.internal.api.dto.AccessTokenResponse;
+import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration.Credentials;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AuthenticationApi} handles oAuth2 authentication and token refreshing
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class AuthenticationApi extends RestManager {
+    private static final URI OAUTH_URI = getApiBaseBuilder().path(PATH_OAUTH).build();
+
+    private final ScheduledExecutorService scheduler;
+    private final Logger logger = LoggerFactory.getLogger(AuthenticationApi.class);
+
+    private @Nullable ScheduledFuture<?> refreshTokenJob;
+    private Optional<AccessTokenResponse> tokenResponse = Optional.empty();
+    private String scope = "";
+
+    public AuthenticationApi(ApiBridgeHandler bridge, ScheduledExecutorService scheduler) {
+        super(bridge, FeatureArea.NONE);
+        this.scheduler = scheduler;
+    }
+
+    public void authenticate(Credentials credentials, Set<FeatureArea> features) throws NetatmoException {
+        Set<FeatureArea> requestedFeatures = !features.isEmpty() ? features : FeatureArea.AS_SET;
+        scope = FeatureArea.toScopeString(requestedFeatures);
+        requestToken(credentials.clientId, credentials.clientSecret,
+                Map.of(USERNAME, credentials.username, PASSWORD, credentials.password, SCOPE, scope));
+    }
+
+    private void requestToken(String id, String secret, Map<String, String> entries) throws NetatmoException {
+        Map<String, String> payload = new HashMap<>(entries);
+        payload.putAll(Map.of(GRANT_TYPE, entries.keySet().contains(PASSWORD) ? PASSWORD : REFRESH_TOKEN, CLIENT_ID, id,
+                CLIENT_SECRET, secret));
+        disconnect();
+        AccessTokenResponse response = post(OAUTH_URI, AccessTokenResponse.class, payload);
+        refreshTokenJob = scheduler.schedule(() -> {
+            try {
+                requestToken(id, secret, Map.of(REFRESH_TOKEN, response.getRefreshToken()));
+            } catch (NetatmoException e) {
+                logger.warn("Unable to refresh access token : {}", e.getMessage());
+            }
+        }, Math.round(response.getExpiresIn() * 0.8), TimeUnit.SECONDS);
+        tokenResponse = Optional.of(response);
+    }
+
+    public void disconnect() {
+        tokenResponse = Optional.empty();
+    }
+
+    public void dispose() {
+        ScheduledFuture<?> job = refreshTokenJob;
+        if (job != null) {
+            job.cancel(true);
+        }
+        refreshTokenJob = null;
+    }
+
+    public @Nullable String getAuthorization() {
+        return tokenResponse.map(at -> String.format("Bearer %s", at.getAccessToken())).orElse(null);
+    }
+
+    public boolean matchesScopes(Set<Scope> requiredScopes) {
+        // either we do not require any scope, either connected and all scopes available
+        return requiredScopes.isEmpty()
+                || (isConnected() && tokenResponse.map(at -> at.getScope().containsAll(requiredScopes)).orElse(false));
+    }
+
+    public boolean isConnected() {
+        return !tokenResponse.isEmpty();
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/BodyResponse.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/BodyResponse.java
new file mode 100644 (file)
index 0000000..93feac0
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link BodyResponse} models a response returned by API call containing
+ * a list of elements.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class BodyResponse<T extends NAObject> {
+    @SerializedName(value = "home")
+    private @Nullable T element;
+
+    public @Nullable T getElement() {
+        return element;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EnergyApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EnergyApi.java
new file mode 100644 (file)
index 0000000..f5a3640
--- /dev/null
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * The {@link EnergyApi} handles API endpoints related to Energy feature area
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class EnergyApi extends RestManager {
+    public EnergyApi(ApiBridgeHandler apiClient) {
+        super(apiClient, FeatureArea.ENERGY);
+    }
+
+    /**
+     *
+     * The method switchSchedule switches the home's schedule to another existing schedule.
+     *
+     * @param homeId The id of home (required)
+     * @param scheduleId The schedule id. It can be found in the getthermstate response, under the keys
+     *            therm_program_backup and therm_program. (required)
+     * @throws NetatmoException If fail to call the API, e.g. server error or cannot deserialize the
+     *             response body
+     */
+    public void switchSchedule(String homeId, String scheduleId) throws NetatmoException {
+        UriBuilder uriBuilder = getAppUriBuilder(SUB_PATH_SWITCHSCHEDULE, PARAM_HOMEID, homeId, PARAM_SCHEDULEID,
+                scheduleId);
+        post(uriBuilder, ApiResponse.Ok.class, null, null);
+    }
+
+    /**
+     *
+     * This endpoint permits to control the heating of a specific home. A home can be set in 3 differents modes:
+     * "schedule" mode in which the home will follow the user schedule
+     * "away" mode which will put the whole house to away (default is 12° but can be changed by the user in its
+     * settings)
+     * "hg" corresponds to frostguard mode (7° by default)
+     *
+     * @param homeId The id of home (required)
+     * @param mode The mode. (required)
+     * @throws NetatmoCommunicationException when call failed, e.g. server error or cannot deserialize
+     */
+    public void setThermMode(String homeId, String mode) throws NetatmoException {
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_SETTHERMMODE, PARAM_HOMEID, homeId, PARAM_MODE, mode);
+        post(uriBuilder, ApiResponse.Ok.class, null, null);
+    }
+
+    /**
+     *
+     * The method setThermpoint changes the Thermostat manual temperature setpoint.
+     *
+     * @param homeId The id of home (required)
+     * @param roomId The id of the room (required)
+     * @param mode The mode. (required)
+     * @param endtime For manual or max setpoint_mode, defines when the setpoint expires.
+     * @param temp For manual setpoint_mode, defines the temperature setpoint (in °C)
+     * @throws NetatmoCommunicationException when call failed, e.g. server error or cannot deserialize
+     */
+    public void setThermpoint(String homeId, String roomId, SetpointMode mode, long endtime, double temp)
+            throws NetatmoException {
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_SETROOMTHERMPOINT, PARAM_HOMEID, homeId, PARAM_ROOMID, roomId,
+                PARAM_MODE, mode.apiDescriptor);
+        if (mode == SetpointMode.MANUAL || mode == SetpointMode.MAX) {
+            uriBuilder.queryParam("endtime", endtime);
+            if (mode == SetpointMode.MANUAL) {
+                uriBuilder.queryParam("temp", temp > THERM_MAX_SETPOINT ? THERM_MAX_SETPOINT : temp);
+            }
+        }
+        post(uriBuilder, ApiResponse.Ok.class, null, null);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/HomeApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/HomeApi.java
new file mode 100644 (file)
index 0000000..5ce35d0
--- /dev/null
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import java.util.Collection;
+import java.util.Set;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.NAHomeStatusResponse;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * The {@link HomeApi} handles general API endpoints not requiring specific scope area
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeApi extends RestManager {
+
+    public HomeApi(ApiBridgeHandler apiClient) {
+        super(apiClient, FeatureArea.NONE);
+    }
+
+    public @Nullable HomeStatus getHomeStatus(String homeId) throws NetatmoException {
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMESTATUS, PARAM_HOMEID, homeId);
+
+        NAHomeStatusResponse response = get(uriBuilder, NAHomeStatusResponse.class);
+        NAHomeStatus body = response.getBody();
+        return body != null ? body.getHomeStatus().orElse(null) : null;
+    }
+
+    public @Nullable HomeData getHomeData(String homeId) throws NetatmoException {
+        Collection<HomeData> result = getHomesData(homeId, null);
+        return result.isEmpty() ? null : result.iterator().next();
+    }
+
+    public Collection<HomeData> getHomesData(@Nullable String homeId, @Nullable ModuleType type)
+            throws NetatmoException {
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMES_DATA, PARAM_HOMEID, homeId);
+
+        if (type != null) {
+            uriBuilder.queryParam(PARAM_GATEWAYTYPE, type.name());
+        }
+
+        HomeData.HomesDataResponse response = get(uriBuilder, HomeData.HomesDataResponse.class);
+        ListBodyResponse<HomeData> body = response.getBody();
+        return body != null ? body.getElements() : Set.of();
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ListBodyResponse.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ListBodyResponse.java
new file mode 100644 (file)
index 0000000..2520dc6
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import java.util.Collection;
+
+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.deserialization.NAObjectMap;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link ListBodyResponse} models a response returned by API call containing
+ * a list of elements.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ListBodyResponse<T extends NAObject> {
+    @SerializedName(value = "devices", alternate = { "homes", "events_list", "events" })
+    private NAObjectMap<T> elements = new NAObjectMap<>();
+
+    @Nullable
+    T getElement(String id) {
+        return elements.get(id);
+    }
+
+    public Collection<T> getElements() {
+        return elements.values();
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoException.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoException.java
new file mode 100644 (file)
index 0000000..709f205
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError;
+
+/**
+ * An exception that occurred while communicating with Netatmo server or related processes.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NetatmoException extends IOException {
+    private static final long serialVersionUID = 1513549973502021727L;
+    private ServiceError statusCode = ServiceError.UNKNOWN;
+
+    public NetatmoException(String format, Object... args) {
+        super(String.format(format, args));
+    }
+
+    public NetatmoException(Exception e, String format, Object... args) {
+        super(String.format(format, args), e);
+    }
+
+    public NetatmoException(String message) {
+        super(message);
+    }
+
+    public NetatmoException(ApiError error) {
+        super(error.getMessage());
+        this.statusCode = error.getCode();
+    }
+
+    public ServiceError getStatusCode() {
+        return statusCode;
+    }
+
+    @Override
+    public @Nullable String getMessage() {
+        String message = super.getMessage();
+        return message == null ? null
+                : String.format("Rest call failed: statusCode=%s, message=%s", statusCode, message);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/RestManager.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/RestManager.java
new file mode 100644 (file)
index 0000000..c556a8a
--- /dev/null
@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import static org.eclipse.jetty.http.HttpMethod.POST;
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * Base class for all various rest managers
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public abstract class RestManager {
+    private static final UriBuilder API_BASE_BUILDER = UriBuilder.fromUri(URL_API);
+    private static final UriBuilder APP_URI_BUILDER = UriBuilder.fromUri(URL_APP).path(PATH_API);
+    private static final UriBuilder API_URI_BUILDER = getApiBaseBuilder().path(PATH_API);
+
+    private final Set<Scope> requiredScopes;
+    private final ApiBridgeHandler apiBridge;
+
+    public RestManager(ApiBridgeHandler apiBridge, FeatureArea features) {
+        this.requiredScopes = features.scopes;
+        this.apiBridge = apiBridge;
+    }
+
+    protected <T extends ApiResponse<?>> T get(UriBuilder uriBuilder, Class<T> clazz) throws NetatmoException {
+        return executeUri(uriBuilder, HttpMethod.GET, clazz, null, null);
+    }
+
+    protected <T extends ApiResponse<?>> T post(UriBuilder uriBuilder, Class<T> clazz, @Nullable String payload,
+            @Nullable String contentType) throws NetatmoException {
+        return executeUri(uriBuilder, HttpMethod.POST, clazz, payload, contentType);
+    }
+
+    protected <T> T post(URI uri, Class<T> clazz, Map<String, String> entries) throws NetatmoException {
+        return apiBridge.executeUri(uri, POST, clazz, toRequest(entries),
+                "application/x-www-form-urlencoded;charset=UTF-8", 3);
+    }
+
+    private <T extends ApiResponse<?>> T executeUri(UriBuilder uriBuilder, HttpMethod method, Class<T> clazz,
+            @Nullable String payload, @Nullable String contentType) throws NetatmoException {
+        URI uri = uriBuilder.build();
+        T response = apiBridge.executeUri(uri, method, clazz, payload, contentType, 3);
+        if (response instanceof ApiResponse.Ok && ((ApiResponse.Ok) response).failed()) {
+            throw new NetatmoException("Command failed : %s for uri : %s", response.getStatus(), uri.toString());
+        }
+        return response;
+    }
+
+    private static UriBuilder appendParams(UriBuilder builder, @Nullable Object... params) {
+        if (params.length % 2 != 0) {
+            throw new IllegalArgumentException("appendParams : params count must be even");
+        }
+        for (int i = 0; i < params.length; i += 2) {
+            Object query = params[i];
+            if (query instanceof String) {
+                Object param = params[i + 1];
+                if (param != null) {
+                    builder.queryParam((String) query, param);
+                }
+            } else {
+                throw new IllegalArgumentException("appendParams : even parameters must be Strings");
+            }
+        }
+        return builder;
+    }
+
+    protected static UriBuilder getApiBaseBuilder() {
+        return API_BASE_BUILDER.clone();
+    }
+
+    public static UriBuilder getApiUriBuilder(String path, @Nullable Object... params) {
+        return appendParams(API_URI_BUILDER.clone().path(path), params);
+    }
+
+    protected static UriBuilder getAppUriBuilder(String path, @Nullable Object... params) {
+        return appendParams(APP_URI_BUILDER.clone().path(path), params);
+    }
+
+    private String toRequest(Map<String, String> entries) {
+        return entries.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
+    }
+
+    public Set<Scope> getRequiredScopes() {
+        return requiredScopes;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/SecurityApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/SecurityApi.java
new file mode 100644 (file)
index 0000000..3640144
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
+import org.openhab.binding.netatmo.internal.api.dto.Home;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent.NAEventsDataResponse;
+import org.openhab.binding.netatmo.internal.api.dto.Ping;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * Base class for all Security related endpoints
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class SecurityApi extends RestManager {
+    public SecurityApi(ApiBridgeHandler apiClient) {
+        super(apiClient, FeatureArea.SECURITY);
+    }
+
+    /**
+     * Dissociates a webhook from a user.
+     *
+     * @throws NetatmoException If fail to call the API, e.g. server error or deserializing
+     */
+    public void dropWebhook() throws NetatmoException {
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_DROPWEBHOOK);
+        post(uriBuilder, ApiResponse.Ok.class, null, null);
+    }
+
+    /**
+     * Links a callback url to a user.
+     *
+     * @param uri Your webhook callback url (required)
+     * @throws NetatmoException If fail to call the API, e.g. server error or deserializing
+     */
+    public void addwebhook(URI uri) throws NetatmoException {
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_ADDWEBHOOK, PARAM_URL, uri.toString());
+        post(uriBuilder, ApiResponse.Ok.class, null, null);
+    }
+
+    public Collection<HomeEvent> getPersonEvents(String homeId, String personId) throws NetatmoException {
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETEVENTS, PARAM_HOMEID, homeId, PARAM_PERSONID, personId,
+                PARAM_OFFSET, 1);
+        NAEventsDataResponse response = get(uriBuilder, NAEventsDataResponse.class);
+        BodyResponse<Home> body = response.getBody();
+        if (body != null) {
+            Home home = body.getElement();
+            if (home != null) {
+                return home.getEvents().stream().filter(event -> personId.equals(event.getPersonId()))
+                        .collect(Collectors.toList());
+            }
+        }
+        throw new NetatmoException("home should not be null");
+    }
+
+    public Collection<HomeEvent> getCameraEvents(String homeId, String cameraId) throws NetatmoException {
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETEVENTS, PARAM_HOMEID, homeId, PARAM_DEVICEID, cameraId);
+        NAEventsDataResponse response = get(uriBuilder, NAEventsDataResponse.class);
+        BodyResponse<Home> body = response.getBody();
+        if (body != null) {
+            Home home = body.getElement();
+            if (home != null) {
+                return home.getEvents();
+            }
+        }
+        throw new NetatmoException("home should not be null");
+    }
+
+    public String ping(String vpnUrl) throws NetatmoException {
+        UriBuilder uriBuilder = UriBuilder.fromUri(vpnUrl).path(PATH_COMMAND).path(SUB_PATH_PING);
+        Ping response = get(uriBuilder, Ping.class);
+        return response.getStatus();
+    }
+
+    public void changeStatus(String localCameraURL, boolean setOn) throws NetatmoException {
+        UriBuilder uriBuilder = UriBuilder.fromUri(localCameraURL).path(PATH_COMMAND).path(SUB_PATH_CHANGESTATUS);
+        uriBuilder.queryParam(PARAM_STATUS, setOn ? "on" : "off");
+        post(uriBuilder, ApiResponse.Ok.class, null, null);
+    }
+
+    public void changeFloodLightMode(String localCameraURL, FloodLightMode mode) throws NetatmoException {
+        UriBuilder uriBuilder = UriBuilder.fromUri(localCameraURL).path(PATH_COMMAND).path(SUB_PATH_FLOODLIGHTSET);
+        uriBuilder.queryParam("config", "%7B%22mode%22:%22" + mode.toString() + "%22%7D");
+        get(uriBuilder, ApiResponse.Ok.class);
+    }
+
+    public void setPersonAwayStatus(String homeId, String personId, boolean away) throws NetatmoException {
+        UriBuilder uriBuilder = getAppUriBuilder(away ? SUB_PATH_PERSON_AWAY : SUB_PATH_PERSON_HOME);
+        String payload = String.format(
+                away ? "{\"home_id\":\"%s\",\"person_id\":\"%s\"}" : "{\"home_id\":\"%s\",\"person_ids\":[\"%s\"]}",
+                homeId, personId);
+        post(uriBuilder, ApiResponse.Ok.class, payload, "application/json;charset=utf-8");
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java
new file mode 100644 (file)
index 0000000..336b432
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.dto.MeasureBodyElem;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain.StationDataResponse;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * Base class for all Weather related endpoints
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class WeatherApi extends RestManager {
+    private class NAMeasuresResponse extends ApiResponse<List<MeasureBodyElem<Double>>> {
+    }
+
+    private class NADateMeasuresResponse extends ApiResponse<List<MeasureBodyElem<ZonedDateTime>>> {
+    }
+
+    public WeatherApi(ApiBridgeHandler apiClient) {
+        super(apiClient, FeatureArea.WEATHER);
+    }
+
+    /**
+     *
+     * Returns data from a user's Weather Stations (measures and device specific data);
+     *
+     * @param deviceId Id of the device you want to retrieve information of (optional)
+     * @param getFavorites Whether to include the user's favorite Weather Stations in addition to the user's
+     *            own Weather Stations (optional, default to false)
+     * @return StationDataResponse
+     * @throws NetatmoException If fail to call the API, e.g. server error or deserializing
+     */
+    public StationDataResponse getStationsData(@Nullable String deviceId, boolean getFavorites)
+            throws NetatmoException {
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETSTATION, PARAM_DEVICEID, deviceId, //
+                PARAM_FAVORITES, getFavorites);
+        StationDataResponse response = get(uriBuilder, StationDataResponse.class);
+        return response;
+    }
+
+    public NAMain getStationData(String deviceId) throws NetatmoException {
+        ListBodyResponse<NAMain> answer = getStationsData(deviceId, true).getBody();
+        if (answer != null) {
+            NAMain station = answer.getElement(deviceId);
+            if (station != null) {
+                return station;
+            }
+        }
+        throw new NetatmoException("Unexpected answer searching device '%s' : not found.", deviceId);
+    }
+
+    public @Nullable Object getMeasures(String deviceId, @Nullable String moduleId, @Nullable String scale,
+            String apiDescriptor) throws NetatmoException {
+        MeasureBodyElem<?> result = getMeasure(deviceId, moduleId, scale, apiDescriptor);
+        return result.getSingleValue();
+    }
+
+    public @Nullable Object getMeasures(String deviceId, @Nullable String moduleId, @Nullable String scale,
+            String apiDescriptor, String limit) throws NetatmoException {
+        String queryLimit = limit;
+        if (!apiDescriptor.contains("_")) {
+            queryLimit += "_" + apiDescriptor;
+        }
+
+        MeasureBodyElem<?> result = getMeasure(deviceId, moduleId, scale, queryLimit.toLowerCase());
+        return result.getSingleValue();
+    }
+
+    private MeasureBodyElem<?> getMeasure(String deviceId, @Nullable String moduleId, @Nullable String scale,
+            String measureType) throws NetatmoException {
+        // NAMeasuresResponse is not designed for optimize=false
+        UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETMEASURE, PARAM_DEVICEID, deviceId, "real_time", true,
+                "date_end", "last", "optimize", true, "type", measureType.toLowerCase(), PARAM_MODULEID, moduleId);
+
+        if (scale != null) {
+            uriBuilder.queryParam("scale", scale.toLowerCase());
+        }
+        if (measureType.startsWith("date")) {
+            NADateMeasuresResponse response = get(uriBuilder, NADateMeasuresResponse.class);
+            List<MeasureBodyElem<ZonedDateTime>> body = response.getBody();
+            if (body != null && !body.isEmpty()) {
+                return body.get(0);
+            }
+        } else {
+            NAMeasuresResponse response = get(uriBuilder, NAMeasuresResponse.class);
+            List<MeasureBodyElem<Double>> body = response.getBody();
+            if (body != null && !body.isEmpty()) {
+                return body.get(0);
+            }
+        }
+        throw new NetatmoException("Empty response while getting measurements");
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java
new file mode 100644 (file)
index 0000000..e48f4f3
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2022 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.api.data;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This enum describes sub events in relation to a given event
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public enum EventSubType {
+    SD_CARD_MISSING(List.of(EventType.SD), 1),
+    SD_CARD_INSERTED(List.of(EventType.SD), 2),
+    SD_CARD_FORMATTED(List.of(EventType.SD), 3),
+    SD_CARD_WORKING(List.of(EventType.SD), 4),
+    SD_CARD_DEFECTIVE(List.of(EventType.SD), 5),
+    SD_CARD_INCOMPATIBLE_SPEED(List.of(EventType.SD), 6),
+    SD_CARD_INSUFFICIENT_SPACE(List.of(EventType.SD), 7),
+    ALIM_INCORRECT_POWER(List.of(EventType.ALIM), 1),
+    ALIM_CORRECT_POWER(List.of(EventType.ALIM), 2),
+
+    // Artificially implemented by the binding subtypes
+    PERSON_ARRIVAL(List.of(EventType.PERSON, EventType.PERSON_HOME), 1),
+    PERSON_SEEN(List.of(EventType.PERSON), 2),
+    PERSON_DEPARTURE(List.of(EventType.PERSON_AWAY), 1),
+    MOVEMENT_HUMAN(List.of(EventType.MOVEMENT, EventType.HUMAN), 1),
+    MOVEMENT_VEHICLE(List.of(EventType.MOVEMENT), 2),
+    MOVEMENT_ANIMAL(List.of(EventType.MOVEMENT, EventType.ANIMAL), 3);
+
+    public final List<EventType> types;
+    public final int subType;
+
+    EventSubType(List<EventType> types, int i) {
+        this.types = types;
+        this.subType = i;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java
new file mode 100644 (file)
index 0000000..6f48291
--- /dev/null
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2010-2022 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.api.data;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This enum describes events generated by webhooks and the type of
+ * module they are related to according to API documentation
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public enum EventType {
+    UNKNOWN(),
+
+    @SerializedName("person") // When the Indoor Camera detects a face
+    PERSON(ModuleType.PERSON, ModuleType.WELCOME),
+
+    @SerializedName("person_away") // When geofencing indicates that the person has left the home
+    PERSON_AWAY(ModuleType.PERSON, ModuleType.HOME),
+
+    @SerializedName("person_home") // When the person is declared at home
+    PERSON_HOME(ModuleType.PERSON, ModuleType.HOME),
+
+    @SerializedName("outdoor") // When the Outdoor Camera detects a human, a car or an animal
+    OUTDOOR(ModuleType.PRESENCE, ModuleType.DOORBELL),
+
+    @SerializedName("daily_summary") // When the Outdoor Camera video summary of the last 24 hours is available
+    DAILY_SUMMARY(ModuleType.PRESENCE),
+
+    @SerializedName("movement") // When the Indoor Camera detects motion
+    MOVEMENT(ModuleType.WELCOME),
+
+    @SerializedName("human") // When the Indoor Camera detects human motion
+    HUMAN(ModuleType.WELCOME),
+
+    @SerializedName("animal") // When the Indoor Camera detects animal motion
+    ANIMAL(ModuleType.WELCOME),
+
+    @SerializedName("new_module") // A new Module has been paired with the Indoor Camera
+    NEW_MODULE(ModuleType.WELCOME),
+
+    @SerializedName("module_connect") // Module is connected with the Indoor Camera
+    MODULE_CONNECT(ModuleType.WELCOME),
+
+    @SerializedName("module_disconnect") // Module lost its connection with the Indoor Camera
+    MODULE_DISCONNECT(ModuleType.WELCOME),
+
+    @SerializedName("module_low_battery") // Module's battery is low
+    MODULE_LOW_BATTERY(ModuleType.WELCOME),
+
+    @SerializedName("module_end_update") // Module's firmware update is over
+    MODULE_END_UPDATE(ModuleType.WELCOME),
+
+    @SerializedName("connection") // When the Camera connects to Netatmo servers
+    CONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+    @SerializedName("disconnection") // When the Camera loses connection with Netatmo servers
+    DISCONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+    @SerializedName("on") // When Camera Monitoring is resumed
+    ON(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+    @SerializedName("off") // When Camera Monitoring is turned off
+    OFF(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+    @SerializedName("boot") // When the Camera is booting
+    BOOT(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+    @SerializedName("sd") // When Camera SD Card status changes
+    SD(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+    @SerializedName("alim") // When Camera power supply status changes
+    ALIM(ModuleType.WELCOME, ModuleType.PRESENCE);
+
+    private final Set<ModuleType> appliesTo;
+
+    EventType(ModuleType... appliesTo) {
+        this.appliesTo = Set.of(appliesTo);
+    }
+
+    @Override
+    public String toString() {
+        return name().toLowerCase();
+    }
+
+    public boolean appliesOn(ModuleType searched) {
+        return appliesTo.contains(searched);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java
new file mode 100644 (file)
index 0000000..82a2c7e
--- /dev/null
@@ -0,0 +1,225 @@
+/**
+ * Copyright (c) 2010-2022 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.api.data;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID;
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import java.net.URI;
+import java.util.EnumSet;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.handler.capability.AirCareCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.CameraCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.Capability;
+import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
+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.PersonCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability;
+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;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityExtChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.BatteryChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.BatteryExtChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.EventChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.EventPersonChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.HomeEnergyChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.HomeSecurityChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.HumidityChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.LocationChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.MeasuresChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.NoiseChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.PersonChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.PresenceChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.PressureChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.PressureExtChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.RainChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.RoomChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.SetpointChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.SignalChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureExtChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureOutChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.Therm1ChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampExtChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.WindChannelHelper;
+import org.openhab.core.thing.ThingTypeUID;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This enum all handled Netatmo modules and devices along with their capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public enum ModuleType {
+    UNKNOWN(FeatureArea.NONE, null, null, List.of(), List.of()),
+    ACCOUNT(FeatureArea.NONE, null, null, List.of(), List.of()),
+    @SerializedName("NAHome")
+    HOME(FeatureArea.NONE, "NAHome", ACCOUNT,
+            List.of(DeviceCapability.class, EventCapability.class, HomeCapability.class, ChannelHelperCapability.class),
+            List.of(HomeSecurityChannelHelper.class, HomeEnergyChannelHelper.class)),
+    @SerializedName("NAPerson")
+    PERSON(FeatureArea.SECURITY, "NAPerson", HOME,
+            List.of(EventCapability.class, PersonCapability.class, ChannelHelperCapability.class),
+            List.of(PersonChannelHelper.class, EventPersonChannelHelper.class)),
+    @SerializedName("NACamera")
+    WELCOME(FeatureArea.SECURITY, "NACamera", HOME,
+            List.of(EventCapability.class, CameraCapability.class, ChannelHelperCapability.class),
+            List.of(CameraChannelHelper.class, SignalChannelHelper.class, EventChannelHelper.class)),
+    @SerializedName("NOC")
+    PRESENCE(FeatureArea.SECURITY, "NOC", HOME,
+            List.of(EventCapability.class, PresenceCapability.class, ChannelHelperCapability.class),
+            List.of(CameraChannelHelper.class, PresenceChannelHelper.class, SignalChannelHelper.class,
+                    EventChannelHelper.class)),
+    @SerializedName("NIS")
+    SIREN(FeatureArea.SECURITY, "NIS", HOME, List.of(ChannelHelperCapability.class),
+            List.of(BatteryChannelHelper.class, TimestampChannelHelper.class, SignalChannelHelper.class)),
+    @SerializedName("NDB")
+    DOORBELL(FeatureArea.SECURITY, "NDB", HOME, List.of(ChannelHelperCapability.class),
+            List.of(SignalChannelHelper.class)),
+    @SerializedName("NAMain")
+    WEATHER_STATION(FeatureArea.WEATHER, "NAMain", ACCOUNT,
+            List.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class,
+                    ChannelHelperCapability.class),
+            List.of(PressureExtChannelHelper.class, NoiseChannelHelper.class, HumidityChannelHelper.class,
+                    TemperatureExtChannelHelper.class, AirQualityChannelHelper.class, LocationChannelHelper.class,
+                    TimestampExtChannelHelper.class, MeasuresChannelHelper.class, SignalChannelHelper.class)),
+    @SerializedName("NAModule1")
+    OUTDOOR(FeatureArea.WEATHER, "NAModule1", WEATHER_STATION,
+            List.of(MeasureCapability.class, ChannelHelperCapability.class),
+            List.of(HumidityChannelHelper.class, TemperatureOutChannelHelper.class, BatteryChannelHelper.class,
+                    MeasuresChannelHelper.class, TimestampExtChannelHelper.class, SignalChannelHelper.class)),
+    @SerializedName("NAModule2")
+    WIND(FeatureArea.WEATHER, "NAModule2", WEATHER_STATION, List.of(ChannelHelperCapability.class),
+            List.of(WindChannelHelper.class, BatteryChannelHelper.class, TimestampExtChannelHelper.class,
+                    SignalChannelHelper.class)),
+    @SerializedName("NAModule3")
+    RAIN(FeatureArea.WEATHER, "NAModule3", WEATHER_STATION,
+            List.of(MeasureCapability.class, ChannelHelperCapability.class),
+            List.of(RainChannelHelper.class, BatteryChannelHelper.class, MeasuresChannelHelper.class,
+                    TimestampExtChannelHelper.class, SignalChannelHelper.class)),
+    @SerializedName("NAModule4")
+    INDOOR(FeatureArea.WEATHER, "NAModule4", WEATHER_STATION,
+            List.of(MeasureCapability.class, ChannelHelperCapability.class),
+            List.of(HumidityChannelHelper.class, TemperatureExtChannelHelper.class, AirQualityChannelHelper.class,
+                    BatteryChannelHelper.class, MeasuresChannelHelper.class, TimestampExtChannelHelper.class,
+                    SignalChannelHelper.class)),
+    @SerializedName("NHC")
+    HOME_COACH(FeatureArea.AIR_CARE, "NHC", ACCOUNT,
+            List.of(DeviceCapability.class, AirCareCapability.class, MeasureCapability.class,
+                    ChannelHelperCapability.class),
+            List.of(NoiseChannelHelper.class, HumidityChannelHelper.class, AirQualityExtChannelHelper.class,
+                    TemperatureChannelHelper.class, PressureChannelHelper.class, TimestampExtChannelHelper.class,
+                    SignalChannelHelper.class, MeasuresChannelHelper.class, LocationChannelHelper.class)),
+    @SerializedName("NAPlug")
+    PLUG(FeatureArea.ENERGY, "NAPlug", HOME, List.of(ChannelHelperCapability.class),
+            List.of(SignalChannelHelper.class)),
+    @SerializedName("NATherm1")
+    THERMOSTAT(FeatureArea.ENERGY, "NATherm1", HOME, List.of(ChannelHelperCapability.class),
+            List.of(Therm1ChannelHelper.class, BatteryExtChannelHelper.class, SignalChannelHelper.class)),
+    @SerializedName("NARoom")
+    ROOM(FeatureArea.ENERGY, "NARoom", HOME, List.of(RoomCapability.class, ChannelHelperCapability.class),
+            List.of(RoomChannelHelper.class, SetpointChannelHelper.class)),
+    @SerializedName("NRV")
+    VALVE(FeatureArea.ENERGY, "NRV", HOME, List.of(ChannelHelperCapability.class),
+            List.of(BatteryExtChannelHelper.class, SignalChannelHelper.class));
+
+    public static final EnumSet<ModuleType> AS_SET = EnumSet.allOf(ModuleType.class);
+
+    private final @Nullable ModuleType bridgeType;
+    public final List<String> groupTypes = new LinkedList<>();
+    public final List<String> extensions = new LinkedList<>();
+    public final List<Class<? extends ChannelHelper>> channelHelpers;
+    public final List<Class<? extends Capability>> capabilities;
+    public final ThingTypeUID thingTypeUID;
+    public final FeatureArea feature;
+    public final @Nullable String apiName;
+
+    ModuleType(FeatureArea feature, @Nullable String apiName, @Nullable ModuleType bridge,
+            List<Class<? extends Capability>> capabilities, List<Class<? extends ChannelHelper>> helpers) {
+        this.channelHelpers = helpers;
+        this.bridgeType = bridge;
+        this.feature = feature;
+        this.capabilities = capabilities;
+        this.apiName = apiName;
+        thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase().replace("_", "-"));
+        try {
+            for (Class<? extends ChannelHelper> helperClass : helpers) {
+                ChannelHelper helper = helperClass.getConstructor().newInstance();
+                groupTypes.addAll(helper.getChannelGroupTypes());
+                extensions.addAll(helper.getExtensibleChannels());
+            }
+        } catch (RuntimeException | ReflectiveOperationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    public boolean isLogical() {
+        return !channelHelpers.contains(SignalChannelHelper.class);
+    }
+
+    public boolean isABridge() {
+        for (ModuleType mt : ModuleType.values()) {
+            if (this.equals(mt.bridgeType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public int[] getSignalLevels() {
+        if (!isLogical()) {
+            return (channelHelpers.contains(BatteryChannelHelper.class)
+                    || channelHelpers.contains(BatteryExtChannelHelper.class)) ? RADIO_SIGNAL_LEVELS
+                            : WIFI_SIGNAL_LEVELS;
+        }
+        throw new IllegalArgumentException(
+                "This should not be called for module type : " + name() + ", please file a bug report.");
+    }
+
+    public ModuleType getBridge() {
+        ModuleType bridge = bridgeType;
+        return bridge != null ? bridge : ModuleType.UNKNOWN;
+    }
+
+    public URI getConfigDescription() {
+        return URI.create(BINDING_ID + ":"
+                + (equals(ACCOUNT) ? "api_bridge"
+                        : equals(HOME) ? "home"
+                                : (isLogical() ? "virtual"
+                                        : ModuleType.UNKNOWN.equals(getBridge()) ? "configurable" : "device")));
+    }
+
+    public static ModuleType from(ThingTypeUID thingTypeUID) {
+        return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst()
+                .orElseThrow(() -> new IllegalArgumentException());
+    }
+
+    public static ModuleType from(String apiName) {
+        return ModuleType.AS_SET.stream().filter(mt -> apiName.equals(mt.apiName)).findFirst()
+                .orElseThrow(() -> new IllegalArgumentException());
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java
new file mode 100644 (file)
index 0000000..65829a0
--- /dev/null
@@ -0,0 +1,401 @@
+/**
+ * Copyright (c) 2010-2022 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.api.data;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.core.library.CoreItemFactory.*;
+import static org.openhab.core.library.unit.MetricPrefix.*;
+
+import java.net.URI;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.types.StateDescriptionFragment;
+import org.openhab.core.types.StateDescriptionFragmentBuilder;
+import org.openhab.core.types.util.UnitUtils;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class holds various definitions and settings provided by the Netatmo
+ * API documentation
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NetatmoConstants {
+    public static class Measure {
+        public final double minValue;
+        public final double maxValue;
+        public final int scale;
+        public final Unit<?> unit;
+
+        private Measure(double minValue, double maxValue, double precision, Unit<?> unit) {
+            this.minValue = minValue;
+            this.maxValue = maxValue;
+            this.unit = unit;
+            String[] splitter = Double.valueOf(precision).toString().split("\\.");
+            if (splitter.length > 1) {
+                int dec = Integer.parseInt(splitter[1]);
+                this.scale = dec > 0 ? Integer.toString(dec).length() : 0;
+            } else {
+                this.scale = 0;
+            }
+        }
+    }
+
+    public static class MeasureChannelDetails {
+        private static final StateDescriptionFragmentBuilder BUILDER = StateDescriptionFragmentBuilder.create();
+        public final URI configURI;
+        public final String itemType;
+        public final StateDescriptionFragment stateDescriptionFragment;
+
+        private MeasureChannelDetails(String measureType, String itemType, String pattern) {
+            this.configURI = URI.create(String.join(":", BINDING_ID, measureType, "config"));
+            this.itemType = itemType;
+            this.stateDescriptionFragment = BUILDER.withReadOnly(true).withPattern(pattern).build();
+        }
+    }
+
+    public enum MeasureClass {
+        INSIDE_TEMPERATURE(0, 50, 0.3, SIUnits.CELSIUS, "temp", "measure", true),
+        OUTSIDE_TEMPERATURE(-40, 65, 0.3, SIUnits.CELSIUS, "temp", "measure", true),
+        HEAT_INDEX(-40, 65, 1, SIUnits.CELSIUS, "", "", false),
+        PRESSURE(260, 1260, 1, HECTO(SIUnits.PASCAL), "pressure", "measure", true),
+        CO2(0, 5000, 50, Units.PARTS_PER_MILLION, "co2", "measure", true),
+        NOISE(35, 120, 1, Units.DECIBEL, "noise", "measure", true),
+        RAIN_QUANTITY(0, 150, 0.1, MILLI(SIUnits.METRE), "sum_rain", "sum_rain", false),
+        RAIN_INTENSITY(0, 150, 0.1, Units.MILLIMETRE_PER_HOUR, "", "", false),
+        WIND_SPEED(0, 160, 1.8, SIUnits.KILOMETRE_PER_HOUR, "", "", false),
+        WIND_ANGLE(0, 360, 5, Units.DEGREE_ANGLE, "", "", false),
+        HUMIDITY(0, 100, 3, Units.PERCENT, "hum", "measure", true);
+
+        public static final EnumSet<MeasureClass> AS_SET = EnumSet.allOf(MeasureClass.class);
+
+        public final Measure measureDefinition;
+        public final String apiDescriptor;
+        public final Map<String, MeasureChannelDetails> channels = new HashMap<>(2);
+
+        MeasureClass(double min, double max, double precision, Unit<?> unit, String apiDescriptor, String confFragment,
+                boolean canScale) {
+            this.measureDefinition = new Measure(min, max, precision, unit);
+            this.apiDescriptor = apiDescriptor;
+            if (!apiDescriptor.isBlank()) {
+                String dimension = UnitUtils.getDimensionName(unit);
+
+                channels.put(String.join("-", apiDescriptor, "measurement"),
+                        new MeasureChannelDetails(confFragment, String.join(":", NUMBER, dimension),
+                                String.format("%%.%df %s", measureDefinition.scale, UnitUtils.UNIT_PLACEHOLDER)));
+                if (canScale) {
+                    channels.put(String.join("-", apiDescriptor, GROUP_TIMESTAMP),
+                            new MeasureChannelDetails(GROUP_TIMESTAMP, DATETIME, "%1$tA, %1$td.%1$tm. %1$tH:%1$tM"));
+                }
+            }
+        }
+    }
+
+    // Netatmo API urls
+    public static final String URL_API = "https://api.netatmo.com/";
+    public static final String URL_APP = "https://app.netatmo.net/";
+    public static final String PATH_OAUTH = "oauth2/token";
+    public static final String PATH_API = "api";
+    public static final String PATH_COMMAND = "command";
+    public static final String SUB_PATH_PERSON_AWAY = "setpersonsaway";
+    public static final String SUB_PATH_PERSON_HOME = "setpersonshome";
+    public static final String SUB_PATH_HOMES_DATA = "homesdata";
+    public static final String SUB_PATH_ADDWEBHOOK = "addwebhook";
+    public static final String SUB_PATH_DROPWEBHOOK = "dropwebhook";
+    public static final String SUB_PATH_SETROOMTHERMPOINT = "setroomthermpoint";
+    public static final String SUB_PATH_SETTHERMMODE = "setthermmode";
+    public static final String SUB_PATH_SWITCHSCHEDULE = "switchschedule";
+    public static final String SUB_PATH_GETSTATION = "getstationsdata";
+    public static final String SUB_PATH_GETMEASURE = "getmeasure";
+    public static final String SUB_PATH_HOMESTATUS = "homestatus";
+    public static final String SUB_PATH_HOMECOACH = "gethomecoachsdata";
+    public static final String SUB_PATH_GETEVENTS = "getevents";
+    public static final String SUB_PATH_PING = "ping";
+    public static final String SUB_PATH_CHANGESTATUS = "changestatus";
+    public static final String SUB_PATH_FLOODLIGHTSET = "floodlight_set_config";
+    public static final String PARAM_DEVICEID = "device_id";
+    public static final String PARAM_MODULEID = "module_id";
+    public static final String PARAM_HOMEID = "home_id";
+    public static final String PARAM_ROOMID = "room_id";
+    public static final String PARAM_PERSONID = "person_id";
+    public static final String PARAM_SCHEDULEID = "schedule_id";
+    public static final String PARAM_OFFSET = "offset";
+    public static final String PARAM_GATEWAYTYPE = "gateway_types";
+    public static final String PARAM_MODE = "mode";
+    public static final String PARAM_URL = "url";
+    public static final String PARAM_FAVORITES = "get_favorites";
+    public static final String PARAM_STATUS = "status";
+
+    // Global variables
+    public static final int THERM_MAX_SETPOINT = 30;
+
+    // Token scopes
+    public static enum Scope {
+        @SerializedName("read_station")
+        READ_STATION,
+        @SerializedName("read_thermostat")
+        READ_THERMOSTAT,
+        @SerializedName("write_thermostat")
+        WRITE_THERMOSTAT,
+        @SerializedName("read_camera")
+        READ_CAMERA,
+        @SerializedName("write_camera")
+        WRITE_CAMERA,
+        @SerializedName("access_camera")
+        ACCESS_CAMERA,
+        @SerializedName("read_presence")
+        READ_PRESENCE,
+        @SerializedName("write_presence")
+        WRITE_PRESENCE,
+        @SerializedName("access_presence")
+        ACCESS_PRESENCE,
+        @SerializedName("read_smokedetector")
+        READ_SMOKEDETECTOR,
+        @SerializedName("read_homecoach")
+        READ_HOMECOACH,
+        @SerializedName("read_doorbell")
+        READ_DOORBELL,
+        @SerializedName("write_doorbell")
+        WRITE_DOORBELL,
+        @SerializedName("access_doorbell")
+        ACCESS_DOORBELL,
+        UNKNOWN;
+    }
+
+    private static final Set<Scope> SMOKE = Set.of(Scope.READ_SMOKEDETECTOR);
+    private static final Set<Scope> WELCOME = Set.of(Scope.READ_CAMERA, Scope.WRITE_CAMERA, Scope.ACCESS_CAMERA);
+    private static final Set<Scope> DOORBELL = Set.of(Scope.READ_DOORBELL, Scope.WRITE_DOORBELL, Scope.ACCESS_DOORBELL);
+    private static final Set<Scope> PRESENCE = Set.of(Scope.READ_PRESENCE, Scope.WRITE_PRESENCE, Scope.ACCESS_PRESENCE);
+
+    // Radio signal quality thresholds
+    static final int[] WIFI_SIGNAL_LEVELS = new int[] { 99, 84, 69, 54 }; // Resp : bad, average, good, full
+    static final int[] RADIO_SIGNAL_LEVELS = new int[] { 90, 80, 70, 60 }; // Resp : low, medium, high, full
+
+    public static enum FeatureArea {
+        AIR_CARE(Scope.READ_HOMECOACH),
+        WEATHER(Scope.READ_STATION),
+        ENERGY(Scope.READ_THERMOSTAT, Scope.WRITE_THERMOSTAT),
+        SECURITY(Stream.of(WELCOME, PRESENCE, SMOKE, DOORBELL).flatMap(Set::stream).toArray(Scope[]::new)),
+        NONE();
+
+        public static final Set<FeatureArea> AS_SET = EnumSet.allOf(FeatureArea.class);
+
+        public static String toScopeString(Set<FeatureArea> featureSet) {
+            return featureSet.stream().map(fa -> fa.scopes).flatMap(Set::stream).map(s -> s.name().toLowerCase())
+                    .collect(Collectors.joining(" "));
+        }
+
+        public final Set<Scope> scopes;
+
+        FeatureArea(Scope... scopes) {
+            this.scopes = Set.of(scopes);
+        }
+    }
+
+    // Thermostat definitions
+    public static enum SetpointMode {
+        @SerializedName("program")
+        PROGRAM("program"),
+        @SerializedName("away")
+        AWAY("away"),
+        @SerializedName("hg")
+        FROST_GUARD("hg"),
+        @SerializedName("manual")
+        MANUAL("manual"),
+        @SerializedName("off")
+        OFF("off"),
+        @SerializedName("max")
+        MAX("max"),
+        @SerializedName("schedule")
+        SCHEDULE("schedule"),
+        HOME("home"),
+        UNKNOWN("");
+
+        public final String apiDescriptor;
+
+        SetpointMode(String descriptor) {
+            this.apiDescriptor = descriptor;
+        }
+    }
+
+    public static enum ThermostatZoneType {
+        @SerializedName("0")
+        DAY("0"),
+        @SerializedName("1")
+        NIGHT("1"),
+        @SerializedName("2")
+        AWAY("2"),
+        @SerializedName("3")
+        FROST_GUARD("3"),
+        @SerializedName("4")
+        CUSTOM("4"),
+        @SerializedName("5")
+        ECO("5"),
+        @SerializedName("8")
+        COMFORT("8"),
+        UNKNOWN("");
+
+        public final String zoneId;
+
+        private ThermostatZoneType(String id) {
+            zoneId = id;
+        }
+    }
+
+    public enum FloodLightMode {
+        @SerializedName("on")
+        ON,
+        @SerializedName("off")
+        OFF,
+        @SerializedName("auto")
+        AUTO,
+        UNKNOWN;
+    }
+
+    public enum EventCategory {
+        @SerializedName("human")
+        HUMAN,
+        @SerializedName("animal")
+        ANIMAL,
+        @SerializedName("vehicle")
+        VEHICLE,
+        UNKNOWN;
+    }
+
+    public enum TrendDescription {
+        @SerializedName("up")
+        UP,
+        @SerializedName("stable")
+        STABLE,
+        @SerializedName("down")
+        DOWN,
+        UNKNOWN;
+    }
+
+    public enum VideoStatus {
+        @SerializedName("recording")
+        RECORDING,
+        @SerializedName("available")
+        AVAILABLE,
+        @SerializedName("deleted")
+        DELETED,
+        UNKNOWN;
+    }
+
+    public enum SdCardStatus {
+        @SerializedName("1")
+        SD_CARD_MISSING,
+        @SerializedName("2")
+        SD_CARD_INSERTED,
+        @SerializedName("3")
+        SD_CARD_FORMATTED,
+        @SerializedName("4")
+        SD_CARD_WORKING,
+        @SerializedName("5")
+        SD_CARD_DEFECTIVE,
+        @SerializedName("6")
+        SD_CARD_INCOMPATIBLE_SPEED,
+        @SerializedName("7")
+        SD_CARD_INSUFFICIENT_SPACE,
+        UNKNOWN;
+    }
+
+    public enum AlimentationStatus {
+        @SerializedName("1")
+        ALIM_INCORRECT_POWER,
+        @SerializedName("2")
+        ALIM_CORRECT_POWER,
+        UNKNOWN;
+    }
+
+    public enum BatteryState {
+        @SerializedName("full")
+        FULL(100),
+        @SerializedName("high")
+        HIGH(80),
+        @SerializedName("medium")
+        MEDIUM(50),
+        @SerializedName("low")
+        LOW(15),
+        UNKNOWN(-1);
+
+        public final int level;
+
+        BatteryState(int i) {
+            this.level = i;
+        }
+    }
+
+    public enum ServiceError {
+        @SerializedName("99")
+        UNKNOWN,
+        @SerializedName("-2")
+        UNKNOWN_ERROR_IN_OAUTH,
+        @SerializedName("-1")
+        GRANT_IS_INVALID,
+        @SerializedName("1")
+        ACCESS_TOKEN_MISSING,
+        @SerializedName("2")
+        INVALID_TOKEN_MISSING,
+        @SerializedName("3")
+        ACCESS_TOKEN_EXPIRED,
+        @SerializedName("5")
+        APPLICATION_DEACTIVATED,
+        @SerializedName("7")
+        NOTHING_TO_MODIFY,
+        @SerializedName("9")
+        DEVICE_NOT_FOUND,
+        @SerializedName("10")
+        MISSING_ARGUMENTS,
+        @SerializedName("13")
+        OPERATION_FORBIDDEN,
+        @SerializedName("19")
+        IP_NOT_FOUND,
+        @SerializedName("21")
+        INVALID_ARGUMENT,
+        @SerializedName("22")
+        APPLICATION_NOT_FOUND,
+        @SerializedName("23")
+        USER_NOT_FOUND,
+        @SerializedName("25")
+        INVALID_DATE,
+        @SerializedName("26")
+        MAXIMUM_USAGE_REACHED,
+        @SerializedName("30")
+        INVALID_REFRESH_TOKEN,
+        @SerializedName("31")
+        METHOD_NOT_FOUND,
+        @SerializedName("35")
+        UNABLE_TO_EXECUTE,
+        @SerializedName("36")
+        PROHIBITED_STRING,
+        @SerializedName("37")
+        NO_MORE_SPACE_AVAILABLE_ON_THE_CAMERA,
+        @SerializedName("40")
+        JSON_GIVEN_HAS_AN_INVALID_ENCODING,
+        @SerializedName("41")
+        DEVICE_IS_UNREACHABLE;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/AccessTokenResponse.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/AccessTokenResponse.java
new file mode 100644 (file)
index 0000000..65bd174
--- /dev/null
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.List;
+
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
+
+/**
+ * This is the Access Token Response, a simple value-object holding the result of an Access Token Request, as
+ * provided by Netatmo API.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public final class AccessTokenResponse {
+
+    /**
+     * The access token issued by the authorization server. It is used
+     * by the client to gain access to a resource.
+     *
+     */
+    private String accessToken;
+
+    /**
+     * Number of seconds that this OAuthToken is valid for since the time it was created.
+     *
+     */
+    private long expiresIn;
+
+    /**
+     * Refresh token is a string representing the authorization granted to
+     * the client by the resource owner. Unlike access tokens, refresh tokens are
+     * intended for use only with authorization servers and are never sent
+     * to resource servers.
+     *
+     */
+    private String refreshToken;
+
+    private List<Scope> scope;
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public long getExpiresIn() {
+        return expiresIn;
+    }
+
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+
+    public List<Scope> getScope() {
+        return scope;
+    }
+
+    @Override
+    public String toString() {
+        return "AccessTokenResponse [accessToken=" + accessToken + ", expiresIn=" + expiresIn + ", refreshToken="
+                + refreshToken + ", scope=" + scope + "]";
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Dashboard.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Dashboard.java
new file mode 100644 (file)
index 0000000..78ed869
--- /dev/null
@@ -0,0 +1,186 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.TrendDescription;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link Dashboard} holds data returned by API call supporting the dashboard functionality.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class Dashboard {
+    private @Nullable ZonedDateTime timeUtc;
+
+    @SerializedName("BoilerOn")
+    private int boilerOn;
+
+    @SerializedName("BoilerOff")
+    private int boilerOff;
+
+    @SerializedName("Temperature")
+    private double temperature;
+
+    private TrendDescription pressureTrend = TrendDescription.UNKNOWN;
+    private TrendDescription tempTrend = TrendDescription.UNKNOWN;
+    private @Nullable ZonedDateTime dateMaxTemp;
+    private @Nullable ZonedDateTime dateMinTemp;
+    private double minTemp;
+    private double maxTemp;
+    @SerializedName("AbsolutePressure")
+    private double absolutePressure;
+
+    @SerializedName("CO2")
+    private double co2;
+
+    @SerializedName("Humidity")
+    private double humidity;
+
+    @SerializedName("Noise")
+    private double noise;
+
+    @SerializedName("Pressure")
+    private double pressure;
+
+    @SerializedName("Rain")
+    private double rain;
+    @SerializedName("sum_rain_1")
+    private double sumRain1;
+    @SerializedName("sum_rain_24")
+    private double sumRain24;
+
+    @SerializedName("WindAngle")
+    private int windAngle;
+
+    @SerializedName("GustAngle")
+    private int gustAngle;
+
+    @SerializedName("WindStrength")
+    private int windStrength;
+
+    private int maxWindStr;
+    private @Nullable ZonedDateTime dateMaxWindStr;
+
+    @SerializedName("GustStrength")
+    private int gustStrength;
+
+    private int healthIdx;
+
+    public @Nullable ZonedDateTime getTimeUtc() {
+        return timeUtc;
+    }
+
+    public int getBoilerOn() {
+        return boilerOn;
+    }
+
+    public int getBoilerOff() {
+        return boilerOff;
+    }
+
+    public double getTemperature() {
+        return temperature;
+    }
+
+    public TrendDescription getTempTrend() {
+        return tempTrend;
+    }
+
+    public @Nullable ZonedDateTime getDateMaxTemp() {
+        return dateMaxTemp;
+    }
+
+    public @Nullable ZonedDateTime getDateMinTemp() {
+        return dateMinTemp;
+    }
+
+    public double getMinTemp() {
+        return minTemp;
+    }
+
+    public double getMaxTemp() {
+        return maxTemp;
+    }
+
+    public double getAbsolutePressure() {
+        return absolutePressure;
+    }
+
+    public double getCo2() {
+        return co2;
+    }
+
+    public double getHumidity() {
+        return humidity;
+    }
+
+    public double getNoise() {
+        return noise;
+    }
+
+    public double getPressure() {
+        return pressure;
+    }
+
+    public TrendDescription getPressureTrend() {
+        return pressureTrend;
+    }
+
+    public double getRain() {
+        return rain;
+    }
+
+    public double getSumRain1() {
+        return sumRain1;
+    }
+
+    public double getSumRain24() {
+        return sumRain24;
+    }
+
+    public double getWindAngle() {
+        return windAngle;
+    }
+
+    public double getGustAngle() {
+        return gustAngle;
+    }
+
+    public double getWindStrength() {
+        return windStrength;
+    }
+
+    public double getMaxWindStr() {
+        return maxWindStr;
+    }
+
+    public @Nullable ZonedDateTime getDateMaxWindStr() {
+        return dateMaxWindStr;
+    }
+
+    public double getGustStrength() {
+        return gustStrength;
+    }
+
+    public int getHealthIdx() {
+        return healthIdx;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Device.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Device.java
new file mode 100644 (file)
index 0000000..c01d3f9
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+
+/**
+ * The {@link Device} holds common data for all Netatmo devices.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class Device extends NAThing {
+    private @Nullable NAObjectMap<Module> modules;
+    private long dateSetup;
+    private long lastUpgrade;
+    private @Nullable Place place;
+
+    public NAObjectMap<Module> getModules() {
+        NAObjectMap<Module> localModules = modules;
+        return localModules != null ? localModules : new NAObjectMap<>();
+    }
+
+    public long getDateSetup() {
+        return dateSetup;
+    }
+
+    public long getLastUpgrade() {
+        return lastUpgrade;
+    }
+
+    public Optional<Place> getPlace() {
+        return Optional.ofNullable(place);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Event.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Event.java
new file mode 100644 (file)
index 0000000..305815c
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.EventSubType;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link Event} holds information transferred by the webhook.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public abstract class Event extends NAObject {
+    protected EventType type = EventType.UNKNOWN;
+    @SerializedName(value = "camera_id", alternate = { "module_id" })
+    private String cameraId = "";
+    protected int subType = -1;
+
+    public abstract ZonedDateTime getTime();
+
+    public abstract @Nullable String getSnapshotUrl();
+
+    public abstract @Nullable String getPersonId();
+
+    public EventType getEventType() {
+        return type;
+    }
+
+    public String getCameraId() {
+        return cameraId;
+    }
+
+    @Override
+    public @Nullable String getName() {
+        String localMessage = super.getName();
+        return (localMessage != null ? localMessage.replace("<b>", "").replace("</b>", "") : "");
+    }
+
+    public Optional<EventSubType> getSubTypeDescription() {
+        return Stream.of(EventSubType.values()).filter(v -> v.types.contains(getEventType()) && v.subType == subType)
+                .findFirst();
+    }
+
+    public void setEventType(EventType type) {
+        this.type = type;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Home.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Home.java
new file mode 100644 (file)
index 0000000..371927d
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+/**
+ * The {@link Home} holds home information.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class Home extends Device implements Location {
+    private double[] coordinates = {};
+    private double altitude;
+    private List<HomeEvent> events = List.of();
+
+    @Override
+    public ModuleType getType() {
+        return ModuleType.HOME;
+    }
+
+    @Override
+    public double getAltitude() {
+        return altitude;
+    }
+
+    @Override
+    public double[] getCoordinates() {
+        return coordinates;
+    }
+
+    public List<HomeEvent> getEvents() {
+        return events;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeData.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeData.java
new file mode 100644 (file)
index 0000000..2294fe4
--- /dev/null
@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.ApiResponse;
+import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+
+/**
+ * The {@link HomeData} holds home information returned by homesdata endpoint.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeData extends NAThing implements NAModule, LocationEx {
+    public class HomesDataResponse extends ApiResponse<ListBodyResponse<HomeData>> {
+    }
+
+    private double altitude;
+    private double[] coordinates = {};
+    private @Nullable String country;
+    private @Nullable String timezone;
+
+    private @Nullable String temperatureControlMode;
+    private SetpointMode thermMode = SetpointMode.UNKNOWN;
+    private int thermSetpointDefaultDuration;
+    private List<ThermProgram> schedules = List.of();
+
+    private NAObjectMap<HomeDataPerson> persons = new NAObjectMap<>();
+    private NAObjectMap<HomeDataRoom> rooms = new NAObjectMap<>();
+    private NAObjectMap<HomeDataModule> modules = new NAObjectMap<>();
+
+    @Override
+    public ModuleType getType() {
+        return ModuleType.HOME;
+    }
+
+    @Override
+    public double getAltitude() {
+        return altitude;
+    }
+
+    @Override
+    public double[] getCoordinates() {
+        return coordinates;
+    }
+
+    @Override
+    public Optional<String> getCountry() {
+        return Optional.ofNullable(country);
+    }
+
+    @Override
+    public Optional<String> getTimezone() {
+        return Optional.ofNullable(timezone);
+    }
+
+    public int getThermSetpointDefaultDuration() {
+        return thermSetpointDefaultDuration;
+    }
+
+    public SetpointMode getThermMode() {
+        return thermMode;
+    }
+
+    public NAObjectMap<HomeDataPerson> getPersons() {
+        return persons;
+    }
+
+    public List<HomeDataPerson> getKnownPersons() {
+        return persons.values().stream().filter(HomeDataPerson::isKnown).collect(Collectors.toList());
+    }
+
+    public Optional<String> getTemperatureControlMode() {
+        return Optional.ofNullable(temperatureControlMode);
+    }
+
+    public NAObjectMap<HomeDataRoom> getRooms() {
+        return rooms;
+    }
+
+    public NAObjectMap<HomeDataModule> getModules() {
+        return modules;
+    }
+
+    public Set<FeatureArea> getFeatures() {
+        return getModules().values().stream().map(m -> m.getType().feature).collect(Collectors.toSet());
+    }
+
+    public List<ThermProgram> getThermSchedules() {
+        return schedules;
+    }
+
+    public @Nullable ThermProgram getActiveProgram() {
+        return schedules.stream().filter(ThermProgram::isSelected).findFirst().orElse(null);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataModule.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataModule.java
new file mode 100644 (file)
index 0000000..da2c91b
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link HomeDataModule} holds module informations returned by getHomeData endpoint
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeDataModule extends NAThing implements NAModule {
+    private @Nullable ZonedDateTime setupDate;
+    private @Nullable String applianceType;
+    private List<String> moduleBridged = List.of();
+
+    public @Nullable String getApplianceType() {
+        return applianceType;
+    }
+
+    public Optional<ZonedDateTime> getSetupDate() {
+        return Optional.ofNullable(setupDate);
+    }
+
+    public List<String> getModuleBridged() {
+        return moduleBridged;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataPerson.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataPerson.java
new file mode 100644 (file)
index 0000000..278a227
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+/**
+ * The {@link HomeDataPerson} provides Person informations returned by getHomeData endpoint
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeDataPerson extends NAThing implements NAModule {
+    private @Nullable String url;
+
+    @Override
+    public ModuleType getType() {
+        return ModuleType.PERSON;
+    }
+
+    public boolean isKnown() {
+        return description != null;
+    }
+
+    public Optional<String> getUrl() {
+        return Optional.ofNullable(url);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataRoom.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataRoom.java
new file mode 100644 (file)
index 0000000..d89bea2
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+/**
+ * The {@link HomeDataRoom} provides Room informations returned by getHomeData endpoint
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HomeDataRoom extends NAObject implements NAModule {
+    private List<String> moduleIds = List.of();
+
+    @Override
+    public ModuleType getType() {
+        // In json api answer type for NARoom is used with free strings like kitchen, living...
+        return ModuleType.ROOM;
+    }
+
+    public List<String> getModuleIds() {
+        return moduleIds;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeEvent.java
new file mode 100644 (file)
index 0000000..6a56c2c
--- /dev/null
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.ApiResponse;
+import org.openhab.binding.netatmo.internal.api.BodyResponse;
+import org.openhab.binding.netatmo.internal.api.data.EventSubType;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.EventCategory;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.VideoStatus;
+
+/**
+ * The {@link HomeEvent} holds information transferred by the webhook about a home event.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeEvent extends Event {
+    public class NAEventsDataResponse extends ApiResponse<BodyResponse<Home>> {
+    }
+
+    private ZonedDateTime time = ZonedDateTime.now();
+    private @Nullable String personId;
+    private EventCategory category = EventCategory.UNKNOWN;
+    private @Nullable Snapshot snapshot;
+    private @Nullable String videoId;
+    private VideoStatus videoStatus = VideoStatus.UNKNOWN;
+    private boolean isArrival;
+
+    @Override
+    public ZonedDateTime getTime() {
+        return time;
+    }
+
+    @Override
+    public @Nullable String getPersonId() {
+        return personId;
+    }
+
+    public @Nullable String getVideoId() {
+        return videoId;
+    }
+
+    public VideoStatus getVideoStatus() {
+        return videoStatus;
+    }
+
+    @Override
+    public Optional<EventSubType> getSubTypeDescription() {
+        // Blend extra information provided by this kind of event in subcategories...
+        if (type == EventType.PERSON) {
+            subType = isArrival ? EventSubType.PERSON_ARRIVAL.subType : EventSubType.PERSON_SEEN.subType;
+        } else if (type == EventType.PERSON_HOME) {
+            subType = EventSubType.PERSON_ARRIVAL.subType;
+        } else if (type == EventType.PERSON_AWAY) {
+            subType = EventSubType.PERSON_DEPARTURE.subType;
+        } else if (type == EventType.HUMAN) {
+            subType = EventSubType.MOVEMENT_HUMAN.subType;
+        } else if (type == EventType.ANIMAL) {
+            subType = EventSubType.MOVEMENT_ANIMAL.subType;
+        } else {
+            if (category == EventCategory.ANIMAL) {
+                subType = EventSubType.MOVEMENT_ANIMAL.subType;
+            } else if (category == EventCategory.HUMAN) {
+                subType = EventSubType.MOVEMENT_HUMAN.subType;
+            } else if (category == EventCategory.VEHICLE) {
+                subType = EventSubType.MOVEMENT_VEHICLE.subType;
+            }
+        }
+        // ... and let ancestor do his work
+        return super.getSubTypeDescription();
+    }
+
+    @Override
+    public @Nullable String getSnapshotUrl() {
+        Snapshot localSnap = snapshot;
+        return localSnap != null ? localSnap.getUrl() : null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusModule.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusModule.java
new file mode 100644 (file)
index 0000000..ac53c2a
--- /dev/null
@@ -0,0 +1,110 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.AlimentationStatus;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.BatteryState;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SdCardStatus;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link HomeStatusModule} holds module informations returned by getHomeData endpoint
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeStatusModule extends NAThing {
+    private @Nullable String firmwareName;
+    private @Nullable String wifiState;
+    private @Nullable String status;
+    private @Nullable OnOffType monitoring;
+    private FloodLightMode floodlight = FloodLightMode.UNKNOWN;
+    private SdCardStatus sdStatus = SdCardStatus.UNKNOWN;
+    private AlimentationStatus alimStatus = AlimentationStatus.UNKNOWN;
+    private @Nullable String sirenStatus;
+    private @Nullable String vpnUrl;
+    private boolean isLocal;
+    private BatteryState batteryState = BatteryState.UNKNOWN;
+    private int batteryLevel;
+
+    private @Nullable OpenClosedType boilerStatus;
+    private boolean boilerValveComfortBoost;
+
+    public State getBoilerStatus() {
+        OpenClosedType status = boilerStatus;
+        return status != null ? status : UnDefType.NULL;
+    }
+
+    public boolean getBoilerValveComfortBoost() {
+        return boilerValveComfortBoost;
+    }
+
+    public Optional<String> getFirmwareName() {
+        return Optional.ofNullable(firmwareName);
+    }
+
+    public Optional<String> getWifiState() {
+        return Optional.ofNullable(wifiState);
+    }
+
+    public Optional<String> getStatus() {
+        return Optional.ofNullable(status);
+    }
+
+    public State getMonitoring() {
+        OnOffType localStatus = monitoring;
+        return localStatus != null ? localStatus : UnDefType.NULL;
+    }
+
+    public FloodLightMode getFloodlight() {
+        return floodlight;
+    }
+
+    public SdCardStatus getSdStatus() {
+        return sdStatus;
+    }
+
+    public AlimentationStatus getAlimStatus() {
+        return alimStatus;
+    }
+
+    public Optional<String> getSirenStatus() {
+        return Optional.ofNullable(sirenStatus);
+    }
+
+    public @Nullable String getVpnUrl() {
+        return vpnUrl;
+    }
+
+    public boolean isLocal() {
+        return isLocal;
+    }
+
+    public BatteryState getBatteryState() {
+        return batteryState;
+    }
+
+    public int getBatteryLevel() {
+        return batteryLevel;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusPerson.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusPerson.java
new file mode 100644 (file)
index 0000000..260a6fd
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+/**
+ * The {@link HomeStatusPerson} provides Person informations returned by getHomeData endpoint
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeStatusPerson extends NAThing {
+    private boolean outOfSight;
+
+    @Override
+    public ModuleType getType() {
+        return ModuleType.PERSON;
+    }
+
+    public boolean isOutOfSight() {
+        return outOfSight;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Location.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Location.java
new file mode 100644 (file)
index 0000000..ae4fb34
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.PointType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link Location} is the common interface for dto holding a location
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public interface Location {
+    double[] getCoordinates();
+
+    double getAltitude();
+
+    default State getLocation() {
+        double[] coordinates = getCoordinates();
+        return coordinates.length != 2 ? UnDefType.UNDEF
+                : new PointType(new DecimalType(coordinates[1]), new DecimalType(coordinates[0]),
+                        new DecimalType(getAltitude()));
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/LocationEx.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/LocationEx.java
new file mode 100644 (file)
index 0000000..d4bf05f
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link LocationEx} is the common interface for dto holding a extra location data
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public interface LocationEx extends Location {
+    public Optional<String> getCountry();
+
+    public Optional<String> getTimezone();
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/MeasureBodyElem.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/MeasureBodyElem.java
new file mode 100644 (file)
index 0000000..8dd8d83
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link MeasureBodyElem} holds a list of values returned by getMeasure endpoint.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class MeasureBodyElem<T> {
+    private List<List<T>> value = List.of();
+
+    public List<List<T>> getValue() {
+        return value;
+    }
+
+    public @Nullable T getSingleValue() {
+        if (!value.isEmpty()) {
+            List<T> first = value.get(0);
+            if (!first.isEmpty()) {
+                return first.get(0);
+            }
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Module.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Module.java
new file mode 100644 (file)
index 0000000..3e3ca3e
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.BatteryState;
+
+/**
+ * The {@link Module} holds status information of a Netatmo module.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class Module extends NAThing {
+    private BatteryState batteryState = BatteryState.UNKNOWN;
+    private int batteryPercent = -1;
+
+    public int getBatteryPercent() {
+        return batteryPercent != -1 ? batteryPercent : batteryState.level;
+    }
+
+    public BatteryState getBatteryState() {
+        return batteryState;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java
new file mode 100644 (file)
index 0000000..1960abe
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.ApiResponse;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+
+/**
+ * The {@link NAHomeStatus} holds data for a given home.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class NAHomeStatus {
+    public class NAHomeStatusResponse extends ApiResponse<NAHomeStatus> {
+    }
+
+    public class HomeStatus extends NAThing {
+        private @Nullable NAObjectMap<HomeStatusModule> modules;
+        private @Nullable NAObjectMap<HomeStatusPerson> persons;
+        private @Nullable NAObjectMap<Room> rooms;
+
+        public NAObjectMap<HomeStatusModule> getModules() {
+            NAObjectMap<HomeStatusModule> localModules = modules;
+            return localModules != null ? localModules : new NAObjectMap<>();
+        }
+
+        public NAObjectMap<HomeStatusPerson> getPersons() {
+            NAObjectMap<HomeStatusPerson> localPersons = persons;
+            return localPersons != null ? localPersons : new NAObjectMap<>();
+        }
+
+        public NAObjectMap<Room> getRooms() {
+            NAObjectMap<Room> localRooms = rooms;
+            return localRooms != null ? localRooms : new NAObjectMap<>();
+        }
+    }
+
+    private @Nullable HomeStatus home;
+
+    public Optional<HomeStatus> getHomeStatus() {
+        return Optional.ofNullable(home);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMain.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMain.java
new file mode 100644 (file)
index 0000000..6d64fa2
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.ApiResponse;
+import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
+
+/**
+ * The {@link NAMain} defines a weather or nhc device.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class NAMain extends Device {
+    public class StationDataResponse extends ApiResponse<ListBodyResponse<NAMain>> {
+    }
+
+    private boolean readOnly;
+
+    /**
+     * true when the user was invited to (or has favorited) a station, false when the user owns it
+     *
+     * @return readOnly
+     **/
+    public boolean isReadOnly() {
+        return readOnly;
+    }
+
+    public boolean hasFreshData(int dataFreshnessLimit) {
+        // check by comparing data freshness
+        ZonedDateTime localLastSeen = lastSeen;
+        if (localLastSeen != null && !getType().isLogical()) {
+            return Duration.between(localLastSeen.toInstant(), Instant.now()).getSeconds() < dataFreshnessLimit;
+        }
+        return true;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAModule.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAModule.java
new file mode 100644 (file)
index 0000000..a4aea48
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+/**
+ * The {@link NAModule} is the common interface for dto holding module informations
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public interface NAModule {
+    public String getId();
+
+    public @Nullable String getName();
+
+    public ModuleType getType();
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAObject.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAObject.java
new file mode 100644 (file)
index 0000000..9ce46a8
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link NAObject} class is the base class for all objects
+ * returned by the Netatmo API.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class NAObject {
+    @SerializedName(value = "id", alternate = { "program_id", "_id", "event_id" })
+    protected String id = "";
+    @SerializedName(value = "name", alternate = { "module_name", "station_name", "pseudo", "message", "key" })
+    protected @Nullable String description;
+    private boolean ignoredForThingUpdate;
+
+    public String getId() {
+        return id;
+    }
+
+    public @Nullable String getName() {
+        return description;
+    }
+
+    public boolean isIgnoredForThingUpdate() {
+        return ignoredForThingUpdate;
+    }
+
+    public void setIgnoredForThingUpdate(boolean ignoredForThingUpdate) {
+        this.ignoredForThingUpdate = ignoredForThingUpdate;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThing.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThing.java
new file mode 100644 (file)
index 0000000..7f0af82
--- /dev/null
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link NAThing} is the base class for devices and modules.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class NAThing extends NAObject implements NAModule {
+    @SerializedName(value = "rf_status", alternate = { "wifi_status", "rf_strength", "wifi_strength" })
+    private int radioStatus = -1;
+    @SerializedName(value = "last_seen", alternate = { "last_therm_seen", "last_status_store", "last_plug_seen",
+            "last_message", "last_activity" })
+    protected @Nullable ZonedDateTime lastSeen;
+    @SerializedName(value = "firmware", alternate = { "firmware_revision" })
+    private @Nullable String firmware;
+    private @Nullable Boolean reachable;
+    private @Nullable Dashboard dashboardData;
+
+    private @Nullable String roomId;
+    private @Nullable String bridge;
+    private ModuleType type = ModuleType.UNKNOWN;
+
+    @Override
+    public ModuleType getType() {
+        return type;
+    }
+
+    public boolean isReachable() {
+        // This is not implemented on all devices/modules, so if absent we consider it is reachable
+        Boolean localReachable = this.reachable;
+        return localReachable != null ? localReachable : true;
+    }
+
+    public void setReachable(boolean reachable) {
+        this.reachable = reachable;
+    }
+
+    public @Nullable Dashboard getDashboardData() {
+        return dashboardData;
+    }
+
+    public @Nullable String getFirmware() {
+        return firmware;
+    }
+
+    public int getRadioStatus() {
+        return radioStatus;
+    }
+
+    public Optional<ZonedDateTime> getLastSeen() {
+        return Optional.ofNullable(lastSeen);
+    }
+
+    /**
+     * @return true if the equipment has no parent, meaning its a device.
+     */
+    public boolean isDevice() {
+        return bridge == null;
+    }
+
+    public @Nullable String getBridge() {
+        return bridge;
+    }
+
+    public @Nullable String getRoomId() {
+        return roomId;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Person.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Person.java
new file mode 100644 (file)
index 0000000..788529a
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+/**
+ * The {@link Person} holds answers provided in webhook events
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class Person extends NAThing {
+    private @Nullable String faceUrl;
+    private boolean isKnown;
+
+    @Override
+    public ModuleType getType() {
+        return ModuleType.PERSON;
+    }
+
+    public @Nullable String getFaceUrl() {
+        return faceUrl;
+    }
+
+    public boolean isKnown() {
+        return isKnown;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Ping.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Ping.java
new file mode 100644 (file)
index 0000000..46efb6f
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.ApiResponse;
+
+/**
+ * The {@link Ping} hold url data for a camera module
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class Ping extends ApiResponse<String> {
+    private String localUrl = "";
+    private @Nullable String productName;
+
+    @Override
+    public String getStatus() {
+        return localUrl;
+    }
+
+    @Override
+    public @Nullable String getBody() {
+        return productName;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Place.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Place.java
new file mode 100644 (file)
index 0000000..6b77459
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link Place} reports location information of a Netatmo system.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class Place implements LocationEx {
+    private @Nullable String city;
+    private @Nullable String country;
+    private @Nullable String timezone;
+    private double altitude;
+    private double[] location = {};
+
+    public Optional<String> getCity() {
+        return Optional.ofNullable(city);
+    }
+
+    @Override
+    public Optional<String> getCountry() {
+        return Optional.ofNullable(country);
+    }
+
+    @Override
+    public Optional<String> getTimezone() {
+        return Optional.ofNullable(timezone);
+    }
+
+    @Override
+    public double getAltitude() {
+        return altitude;
+    }
+
+    @Override
+    public double[] getCoordinates() {
+        return location;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Room.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Room.java
new file mode 100644 (file)
index 0000000..7832524
--- /dev/null
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link Room} holds temperature data for a given room.
+ *
+ * @author Bernhard Kreuz - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class Room extends NAObject implements NAModule {
+    private @Nullable String type;
+    private @Nullable OnOffType anticipating;
+    private boolean openWindow;
+    private @Nullable ZonedDateTime thermSetpointStartTime;
+    private @Nullable ZonedDateTime thermSetpointEndTime;
+    private SetpointMode thermSetpointMode = SetpointMode.UNKNOWN;
+    private int heatingPowerRequest;
+    private double thermMeasuredTemperature;
+    private double thermSetpointTemperature;
+
+    public State isAnticipating() {
+        OnOffType status = anticipating;
+        return status != null ? status : UnDefType.NULL;
+    }
+
+    public State hasOpenedWindows() {
+        return openWindow ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
+    }
+
+    public int getHeatingPowerRequest() {
+        return heatingPowerRequest;
+    }
+
+    public double getMeasuredTemp() {
+        return thermMeasuredTemperature;
+    }
+
+    public SetpointMode getSetpointMode() {
+        return thermSetpointMode;
+    }
+
+    public double getSetpointTemp() {
+        return thermSetpointTemperature;
+    }
+
+    public @Nullable ZonedDateTime getSetpointBegin() {
+        return thermSetpointStartTime;
+    }
+
+    public @Nullable ZonedDateTime getSetpointEnd() {
+        return thermSetpointEndTime;
+    }
+
+    @Override
+    public ModuleType getType() {
+        // Note: In json api answer type for NARoom is used with words like kitchen, living...
+        return ModuleType.ROOM;
+    }
+
+    public @Nullable String getLocation() {
+        return type;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Snapshot.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Snapshot.java
new file mode 100644 (file)
index 0000000..54394a6
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link Snapshot} holds data related to a snapshot.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class Snapshot {
+    private @Nullable String url;
+
+    public @Nullable String getUrl() {
+        return url;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/ThermProgram.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/ThermProgram.java
new file mode 100644 (file)
index 0000000..b9a8051
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+
+/**
+ * The {@link ThermProgram} holds setpoint scheduling information.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class ThermProgram extends NAObject {
+    private NAObjectMap<Zone> zones = new NAObjectMap<>();
+    private List<TimeTableItem> timetable = List.of();
+    private boolean selected;
+
+    public List<TimeTableItem> getTimetable() {
+        return timetable;
+    }
+
+    public boolean isSelected() {
+        return selected;
+    }
+
+    public @Nullable Zone getZone(String id) {
+        return zones.get(id);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/TimeTableItem.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/TimeTableItem.java
new file mode 100644 (file)
index 0000000..22f63cd
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link TimeTableItem} holds the temp scheduling for a given zone.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class TimeTableItem extends NAObject {
+    private int mOffset;
+    private int zoneId;
+
+    public int getMinuteOffset() {
+        return mOffset;
+    }
+
+    public int getZoneId() {
+        return zoneId;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java
new file mode 100644 (file)
index 0000000..eefa6a5
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.deserialization.NAPushType;
+
+/**
+ * The {@link WebhookEvent} is responsible to hold
+ * data given back by the Netatmo API when calling the webhook
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WebhookEvent extends Event {
+    private @NonNullByDefault({}) NAPushType pushType;
+    private String homeId = "";
+    private @Nullable String snapshotUrl;
+    private NAObjectMap<Person> persons = new NAObjectMap<>();
+    // Webhook does not provide the event generation time, so we'll use the event reception time
+    private ZonedDateTime time = ZonedDateTime.now();
+
+    public String getHomeId() {
+        return homeId;
+    }
+
+    public NAObjectMap<Person> getPersons() {
+        return persons;
+    }
+
+    @Override
+    public EventType getEventType() {
+        return pushType.getEvent();
+    }
+
+    @Override
+    public ZonedDateTime getTime() {
+        return time;
+    }
+
+    @Override
+    public @Nullable String getPersonId() {
+        return persons.size() > 0 ? persons.keySet().iterator().next() : null;
+    }
+
+    @Override
+    public @Nullable String getSnapshotUrl() {
+        return snapshotUrl;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Zone.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Zone.java
new file mode 100644 (file)
index 0000000..ad68358
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ThermostatZoneType;
+
+/**
+ * The {@link Zone} holds temperature data for a given zone.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class Zone extends NAObject {
+    private ThermostatZoneType type = ThermostatZoneType.UNKNOWN;
+    private double temp;
+
+    public double getTemp() {
+        return temp;
+    }
+
+    public ThermostatZoneType getType() {
+        return type;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraAddress.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraAddress.java
deleted file mode 100644 (file)
index 1a94eca..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.camera;
-
-import java.util.Objects;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-
-/**
- * {@link CameraAddress} handles the data to address a camera (VPN and local address).
- *
- * @author Sven Strohschein - Initial contribution
- */
-@NonNullByDefault
-public class CameraAddress {
-
-    private final String vpnURL;
-    private final String localURL;
-
-    CameraAddress(final String vpnURL, final String localURL) {
-        this.vpnURL = vpnURL;
-        this.localURL = localURL;
-    }
-
-    public String getVpnURL() {
-        return vpnURL;
-    }
-
-    public String getLocalURL() {
-        return localURL;
-    }
-
-    /**
-     * Checks if the VPN URL was changed / isn't equal to the given VPN-URL.
-     * 
-     * @param vpnURL old / known VPN URL
-     * @return true, when the VPN URL isn't equal given VPN URL, otherwise false
-     */
-    public boolean isVpnURLChanged(String vpnURL) {
-        return !getVpnURL().equals(vpnURL);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object object) {
-        if (this == object) {
-            return true;
-        }
-        if (object == null || getClass() != object.getClass()) {
-            return false;
-        }
-        CameraAddress that = (CameraAddress) object;
-        return vpnURL.equals(that.vpnURL) && localURL.equals(that.localURL);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(vpnURL, localURL);
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraHandler.java
deleted file mode 100644 (file)
index 8fda400..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.camera;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.io.IOException;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.io.net.http.HttpUtil;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.swagger.client.model.NAWelcomeCamera;
-
-/**
- * {@link CameraHandler} is the class used to handle Camera Data
- *
- * @author Sven Strohschein - Initial contribution (partly moved code from NAWelcomeCameraHandler to introduce
- *         inheritance, see NAWelcomeCameraHandler)
- *
- */
-@NonNullByDefault
-public abstract class CameraHandler extends NetatmoModuleHandler<NAWelcomeCamera> {
-
-    private static final String PING_URL_PATH = "/command/ping";
-    private static final String STATUS_CHANGE_URL_PATH = "/command/changestatus";
-    private static final String LIVE_PICTURE = "/live/snapshot_720.jpg";
-
-    private final Logger logger = LoggerFactory.getLogger(CameraHandler.class);
-
-    private Optional<CameraAddress> cameraAddress;
-
-    protected CameraHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-        cameraAddress = Optional.empty();
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-        String channelId = channelUID.getId();
-        switch (channelId) {
-            case CHANNEL_CAMERA_STATUS:
-            case CHANNEL_WELCOME_CAMERA_STATUS:
-                if (command == OnOffType.ON) {
-                    switchVideoSurveillance(true);
-                } else if (command == OnOffType.OFF) {
-                    switchVideoSurveillance(false);
-                }
-                break;
-        }
-        super.handleCommand(channelUID, command);
-    }
-
-    @Override
-    protected void updateProperties(NAWelcomeCamera moduleData) {
-        updateProperties(null, moduleData.getType());
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        switch (channelId) {
-            case CHANNEL_CAMERA_STATUS:
-                return getStatusState();
-            case CHANNEL_CAMERA_SDSTATUS:
-                return getSdStatusState();
-            case CHANNEL_CAMERA_ALIMSTATUS:
-                return getAlimStatusState();
-            case CHANNEL_CAMERA_ISLOCAL:
-                return getIsLocalState();
-            case CHANNEL_CAMERA_LIVEPICTURE_URL:
-                return getLivePictureURLState();
-            case CHANNEL_CAMERA_LIVEPICTURE:
-                return getLivePictureState();
-            case CHANNEL_CAMERA_LIVESTREAM_URL:
-                return getLiveStreamState();
-        }
-        return super.getNAThingProperty(channelId);
-    }
-
-    protected State getStatusState() {
-        return getModule().map(m -> toOnOffType(m.getStatus())).orElse(UnDefType.UNDEF);
-    }
-
-    protected State getSdStatusState() {
-        return getModule().map(m -> toOnOffType(m.getSdStatus())).orElse(UnDefType.UNDEF);
-    }
-
-    protected State getAlimStatusState() {
-        return getModule().map(m -> toOnOffType(m.getAlimStatus())).orElse(UnDefType.UNDEF);
-    }
-
-    protected State getIsLocalState() {
-        return getModule().map(m -> toOnOffType(m.isIsLocal())).orElse(UnDefType.UNDEF);
-    }
-
-    protected State getLivePictureURLState() {
-        return getLivePictureURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
-    }
-
-    protected State getLivePictureState() {
-        Optional<String> livePictureURL = getLivePictureURL();
-        return livePictureURL.isPresent() ? toRawType(livePictureURL.get()) : UnDefType.UNDEF;
-    }
-
-    protected State getLiveStreamState() {
-        return getLiveStreamURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
-    }
-
-    /**
-     * Get the url for the live snapshot
-     *
-     * @return Url of the live snapshot
-     */
-    private Optional<String> getLivePictureURL() {
-        return getVpnUrl().map(u -> u += LIVE_PICTURE);
-    }
-
-    /**
-     * Get the url for the live stream depending wether local or not
-     *
-     * @return Url of the live stream
-     */
-    private Optional<String> getLiveStreamURL() {
-        Optional<String> result = getVpnUrl();
-        if (!result.isPresent()) {
-            return Optional.empty();
-        }
-
-        StringBuilder resultStringBuilder = new StringBuilder(result.get());
-        resultStringBuilder.append("/live/index");
-        if (isLocal()) {
-            resultStringBuilder.append("_local");
-        }
-        resultStringBuilder.append(".m3u8");
-        return Optional.of(resultStringBuilder.toString());
-    }
-
-    private Optional<String> getVpnUrl() {
-        return getModule().map(NAWelcomeCamera::getVpnUrl);
-    }
-
-    public Optional<String> getStreamURL(String videoId) {
-        Optional<String> result = getVpnUrl();
-        if (!result.isPresent()) {
-            return Optional.empty();
-        }
-
-        StringBuilder resultStringBuilder = new StringBuilder(result.get());
-        resultStringBuilder.append("/vod/");
-        resultStringBuilder.append(videoId);
-        resultStringBuilder.append("/index");
-        if (isLocal()) {
-            resultStringBuilder.append("_local");
-        }
-        resultStringBuilder.append(".m3u8");
-        return Optional.of(resultStringBuilder.toString());
-    }
-
-    private boolean isLocal() {
-        return getModule().map(NAWelcomeCamera::isIsLocal).orElse(false);
-    }
-
-    private void switchVideoSurveillance(boolean isOn) {
-        Optional<String> localCameraURL = getLocalCameraURL();
-        if (localCameraURL.isPresent()) {
-            String url = localCameraURL.get() + STATUS_CHANGE_URL_PATH + "?status=";
-            if (isOn) {
-                url += "on";
-            } else {
-                url += "off";
-            }
-            executeGETRequest(url);
-
-            invalidateParentCacheAndRefresh();
-        }
-    }
-
-    protected Optional<String> getLocalCameraURL() {
-        Optional<String> vpnURLOptional = getVpnUrl();
-        Optional<CameraAddress> address = cameraAddress;
-        if (vpnURLOptional.isPresent()) {
-            final String vpnURL = vpnURLOptional.get();
-
-            // The local address is (re-)requested when it wasn't already determined or when the vpn address was
-            // changed.
-            if (!address.isPresent() || address.get().isVpnURLChanged(vpnURL)) {
-                Optional<JSONObject> json = executeGETRequestJSON(vpnURL + PING_URL_PATH);
-                address = json.map(j -> j.optString("local_url", null))
-                        .map(localURL -> new CameraAddress(vpnURL, localURL));
-                cameraAddress = address;
-            }
-        }
-        return address.map(CameraAddress::getLocalURL);
-    }
-
-    private Optional<JSONObject> executeGETRequestJSON(String url) {
-        try {
-            return executeGETRequest(url).map(JSONObject::new);
-        } catch (JSONException e) {
-            logger.warn("Error on parsing the content as JSON!", e);
-        }
-        return Optional.empty();
-    }
-
-    protected Optional<String> executeGETRequest(String url) {
-        try {
-            String content = HttpUtil.executeUrl("GET", url, 5000);
-            if (content != null && !content.isEmpty()) {
-                return Optional.of(content);
-            }
-        } catch (IOException e) {
-            logger.warn("Error on accessing local camera url!", e);
-        }
-        return Optional.empty();
-    }
-
-    @Override
-    protected boolean isReachable() {
-        Optional<NAWelcomeCamera> module = getModule();
-        return module.isPresent() ? !"disconnected".equalsIgnoreCase(module.get().getStatus()) : false;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/BatteryHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/BatteryHelper.java
deleted file mode 100644 (file)
index a12bdf9..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.channelhelper;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link BatteryHelper} handle specific behavior
- * of modules using batteries
- *
- * @author Gaël L'hopital - Initial contribution
- *
- */
-@NonNullByDefault
-public class BatteryHelper {
-    private final Logger logger = LoggerFactory.getLogger(BatteryHelper.class);
-    private int batteryLow;
-
-    private @Nullable Object module;
-
-    public BatteryHelper(String batteryLevels) {
-        List<String> thresholds = Arrays.asList(batteryLevels.split(","));
-        batteryLow = Integer.parseInt(thresholds.get(1));
-    }
-
-    public void setModule(Object module) {
-        this.module = module;
-    }
-
-    public Optional<State> getNAThingProperty(String channelId) {
-        Object module = this.module;
-        if (module != null) {
-            try {
-                if (CHANNEL_BATTERY_LEVEL.equalsIgnoreCase(channelId)
-                        || CHANNEL_LOW_BATTERY.equalsIgnoreCase(channelId)) {
-                    switch (channelId) {
-                        case CHANNEL_BATTERY_LEVEL:
-                            Method getBatteryPercent = module.getClass().getMethod("getBatteryPercent");
-                            Integer batteryPercent = (Integer) getBatteryPercent.invoke(module);
-                            return Optional.of(ChannelTypeUtils.toDecimalType(batteryPercent));
-                        case CHANNEL_LOW_BATTERY:
-                            Method getBatteryVp = module.getClass().getMethod("getBatteryVp");
-                            Integer batteryVp = (Integer) getBatteryVp.invoke(module);
-                            return Optional.of(batteryVp < batteryLow ? OnOffType.ON : OnOffType.OFF);
-                    }
-                }
-            } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
-                    | InvocationTargetException e) {
-                logger.warn("The module has no method to access {} property : {}", channelId, e.getMessage());
-                return Optional.of(UnDefType.NULL);
-            }
-        }
-        return Optional.empty();
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RadioHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RadioHelper.java
deleted file mode 100644 (file)
index 8fb0305..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.channelhelper;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link RadioHelper} handle specific behavior
- * of WIFI or RF devices and modules
- *
- * @author Gaël L'hopital - Initial contribution
- *
- */
-@NonNullByDefault
-public class RadioHelper {
-    private final Logger logger = LoggerFactory.getLogger(RadioHelper.class);
-    private final List<Integer> signalThresholds;
-    private @Nullable Object module;
-
-    public RadioHelper(String signalLevels) {
-        signalThresholds = Stream.of(signalLevels.split(",")).map(Integer::parseInt).collect(Collectors.toList());
-    }
-
-    private int getSignalStrength(int signalLevel) {
-        int level;
-        for (level = 0; level < signalThresholds.size(); level++) {
-            if (signalLevel > signalThresholds.get(level)) {
-                break;
-            }
-        }
-        return level;
-    }
-
-    public void setModule(Object module) {
-        this.module = module;
-    }
-
-    public Optional<State> getNAThingProperty(String channelId) {
-        Object module = this.module;
-        if (module != null) {
-            try {
-                switch (channelId) {
-                    case CHANNEL_RF_STATUS:
-                        Method getRfStatus = module.getClass().getMethod("getRfStatus");
-                        Integer rfStatus = (Integer) getRfStatus.invoke(module);
-                        return Optional.of(new DecimalType(getSignalStrength(rfStatus)));
-                    case CHANNEL_WIFI_STATUS:
-                        Method getWifiStatus = module.getClass().getMethod("getWifiStatus");
-                        Integer wifiStatus = (Integer) getWifiStatus.invoke(module);
-                        return Optional.of(new DecimalType(getSignalStrength(wifiStatus)));
-                }
-            } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
-                    | InvocationTargetException e) {
-                logger.warn("The module has no method to access {} property : {}", channelId, e.getMessage());
-                return Optional.of(UnDefType.NULL);
-            }
-        }
-        return Optional.empty();
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/ApiHandlerConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/ApiHandlerConfiguration.java
new file mode 100644 (file)
index 0000000..782d04c
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+
+/**
+ * The {@link ApiHandlerConfiguration} is responsible for holding configuration
+ * information needed to access Netatmo API and general binding behavior setup
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ApiHandlerConfiguration {
+    public class Credentials {
+        public final String clientId, clientSecret, username, password;
+
+        private Credentials(@Nullable String clientId, @Nullable String clientSecret, @Nullable String username,
+                @Nullable String password) throws NetatmoException {
+            this.clientSecret = checkMandatory(clientSecret, "@text/conf-error-no-client-secret");
+            this.username = checkMandatory(username, "@text/conf-error-no-username");
+            this.password = checkMandatory(password, "@text/conf-error-no-password");
+            this.clientId = checkMandatory(clientId, "@text/conf-error-no-client-id");
+        }
+
+        private String checkMandatory(@Nullable String value, String error) throws NetatmoException {
+            if (value == null || value.isBlank()) {
+                throw new NetatmoException(error);
+            }
+            return value;
+        }
+
+        @Override
+        public String toString() {
+            return "Credentials [clientId=" + clientId + ", username=" + username
+                    + ", password=******, clientSecret=******]";
+        }
+    }
+
+    private @Nullable String clientId;
+    private @Nullable String clientSecret;
+    private @Nullable String username;
+    private @Nullable String password;
+    public @Nullable String webHookUrl;
+    public int reconnectInterval = 300;
+
+    public Credentials getCredentials() throws NetatmoException {
+        return new Credentials(clientId, clientSecret, username, password);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/BindingConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/BindingConfiguration.java
new file mode 100644 (file)
index 0000000..b52ec31
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.config;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+
+/**
+ * The {@link BindingConfiguration} is responsible for holding configuration of the binding itself.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class BindingConfiguration {
+    public Set<FeatureArea> features = Set.of();
+    public boolean readFriends = false;
+
+    public void update(BindingConfiguration newConfig) {
+        this.features = newConfig.features;
+        this.readFriends = newConfig.readFriends;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/MeasureConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/MeasureConfiguration.java
new file mode 100644 (file)
index 0000000..753793e
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link MeasureConfiguration} is responsible for holding
+ * configuration information for measure channels
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class MeasureConfiguration {
+    public String period = "";
+    public String limit = "";
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NAThingConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NAThingConfiguration.java
new file mode 100644 (file)
index 0000000..621dac4
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link NAThingConfiguration} is responsible for holding
+ * configuration information for any Netatmo thing module or device
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NAThingConfiguration {
+    public String id = "";
+    public int refreshInterval = -1;
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBridgeConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBridgeConfiguration.java
deleted file mode 100644 (file)
index 15bad93..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.config;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-
-/**
- * The {@link NetatmoBridgeConfiguration} is responsible for holding
- * configuration informations needed to access Netatmo API
- *
- * @author Gaël L'hopital - Initial contribution
- */
-@NonNullByDefault
-public class NetatmoBridgeConfiguration {
-    public @Nullable String clientId;
-    public @Nullable String clientSecret;
-    public @Nullable String username;
-    public @Nullable String password;
-    public boolean readStation = true;
-    public boolean readThermostat = false;
-    public boolean readHealthyHomeCoach = false;
-    public boolean readWelcome = false;
-    public boolean readPresence = false;
-    public @Nullable String webHookUrl;
-    public int reconnectInterval = 5400;
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADeserializer.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADeserializer.java
new file mode 100644 (file)
index 0000000..1976cf7
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2010-2022 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.deserialization;
+
+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.NetatmoException;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link NADeserializer} is responsible to instantiate suitable Gson (de)serializer
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = NADeserializer.class)
+public class NADeserializer {
+    private final Gson gson;
+
+    @Activate
+    public NADeserializer(@Reference TimeZoneProvider timeZoneProvider) {
+        gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+                .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory())
+                .registerTypeAdapter(NAObjectMap.class, new NAObjectMapDeserializer())
+                .registerTypeAdapter(NAPushType.class, new NAPushTypeDeserializer())
+                .registerTypeAdapter(ZonedDateTime.class,
+                        (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
+                            long netatmoTS = json.getAsJsonPrimitive().getAsLong();
+                            Instant i = Instant.ofEpochSecond(netatmoTS);
+                            return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone());
+                        })
+                .registerTypeAdapter(OnOffType.class,
+                        (JsonDeserializer<OnOffType>) (json, type, jsonDeserializationContext) -> OnOffType
+                                .from(json.getAsJsonPrimitive().getAsString()))
+                .registerTypeAdapter(OpenClosedType.class,
+                        (JsonDeserializer<OpenClosedType>) (json, type, jsonDeserializationContext) -> {
+                            String value = json.getAsJsonPrimitive().getAsString().toUpperCase();
+                            return "TRUE".equals(value) || "1".equals(value) ? OpenClosedType.CLOSED
+                                    : OpenClosedType.OPEN;
+                        })
+                .create();
+    }
+
+    public <T> T deserialize(Class<T> clazz, String json) throws NetatmoException {
+        try {
+            @Nullable
+            T result = gson.fromJson(json, clazz);
+            if (result != null) {
+                return result;
+            }
+            throw new NetatmoException("Deserialization of '%s' resulted in null value", json);
+        } catch (JsonSyntaxException e) {
+            throw new NetatmoException(e, "Unexpected error deserializing '%s'", json);
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java
new file mode 100644 (file)
index 0000000..dc5f64c
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2022 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.deserialization;
+
+import java.util.HashMap;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+
+/**
+ * The {@link NAObjectMap} defines a hashmap of NAObjects identified by their id.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NAObjectMap<T extends NAObject> extends HashMap<String, T> {
+    private static final long serialVersionUID = 7635233672795516649L;
+
+    @Nullable
+    public T put(T thing) {
+        return super.put(thing.getId(), thing);
+    }
+
+    public Optional<T> getOpt(String key) {
+        return Optional.ofNullable(super.get(key));
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMapDeserializer.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMapDeserializer.java
new file mode 100644 (file)
index 0000000..a92bdbd
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2022 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.deserialization;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+/**
+ * The {@link NAObjectMapDeserializer} is a specialized deserializer aimed to transform
+ * a list of `NAObjects` into a map identified by the object's id.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+class NAObjectMapDeserializer implements JsonDeserializer<NAObjectMap<?>> {
+    @Override
+    public @Nullable NAObjectMap<?> deserialize(JsonElement json, Type clazz, JsonDeserializationContext context)
+            throws JsonParseException {
+        ParameterizedType parameterized = (ParameterizedType) clazz;
+        Type[] typeArguments = parameterized.getActualTypeArguments();
+        if (typeArguments.length > 0 && json instanceof JsonArray) {
+            Type objectType = typeArguments[0];
+            NAObjectMap<NAObject> result = new NAObjectMap<>();
+            ((JsonArray) json).forEach(item -> {
+                result.put(context.deserialize(item, objectType));
+            });
+            return result;
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushType.java
new file mode 100644 (file)
index 0000000..096e894
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2022 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.deserialization;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+/**
+ * This class holds data of push_type field
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NAPushType {
+    private final ModuleType moduleType;
+    private final EventType event;
+
+    NAPushType(ModuleType moduleType, EventType event) {
+        this.moduleType = moduleType;
+        this.event = event;
+    }
+
+    public ModuleType getModuleType() {
+        return moduleType;
+    }
+
+    public EventType getEvent() {
+        return event;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushTypeDeserializer.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushTypeDeserializer.java
new file mode 100644 (file)
index 0000000..b99cb4d
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2022 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.deserialization;
+
+import java.lang.reflect.Type;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+/**
+ * Specialized deserializer for push_type field
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+class NAPushTypeDeserializer implements JsonDeserializer<NAPushType> {
+
+    @Override
+    public @Nullable NAPushType deserialize(JsonElement json, Type clazz, JsonDeserializationContext context)
+            throws JsonParseException {
+        String string = json.getAsString();
+        String[] elements = string.split("-");
+        if (elements.length > 1) {
+            try {
+                ModuleType moduleType = ModuleType.from(elements[0]);
+                EventType eventType = EventType.valueOf(elements[1].toUpperCase());
+
+                return new NAPushType(moduleType, eventType);
+            } catch (IllegalArgumentException e) {
+            }
+        }
+        throw new JsonParseException("Error deserializing : " + string);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/StrictEnumTypeAdapterFactory.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/StrictEnumTypeAdapterFactory.java
new file mode 100644 (file)
index 0000000..2fd5d3c
--- /dev/null
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2010-2022 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.deserialization;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * Enforces a fallback to UNKNOWN when deserializing enum types, marked as @NonNull whereas they were valued
+ * to null if the appropriate value is absent. It will give more resilience to the binding when Netatmo API evolves.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+class StrictEnumTypeAdapterFactory implements TypeAdapterFactory {
+    private static final StringReader UNKNOWN = new StringReader("\"UNKNOWN\"");
+
+    @Override
+    public @Nullable <T> TypeAdapter<T> create(@NonNullByDefault({}) Gson gson,
+            @NonNullByDefault({}) TypeToken<T> type) {
+        @SuppressWarnings("unchecked")
+        Class<T> rawType = (Class<T>) type.getRawType();
+        return rawType.isEnum() ? newStrictEnumAdapter(gson.getDelegateAdapter(this, type)) : null;
+    }
+
+    private <T> TypeAdapter<T> newStrictEnumAdapter(@NonNullByDefault({}) TypeAdapter<T> delegateAdapter) {
+        return new TypeAdapter<T>() {
+            @Override
+            public void write(JsonWriter out, @Nullable T value) throws IOException {
+                delegateAdapter.write(out, value);
+            }
+
+            @Override
+            public @Nullable T read(JsonReader in) throws IOException {
+                JsonReader delegateReader = new JsonReader(new StringReader('"' + in.nextString() + '"'));
+                @Nullable
+                T value = delegateAdapter.read(delegateReader);
+                delegateReader.close();
+                if (value == null) {
+                    UNKNOWN.reset();
+                    value = delegateAdapter.read(new JsonReader(UNKNOWN));
+                }
+                return value;
+            }
+        };
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoDiscoveryService.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoDiscoveryService.java
new file mode 100644 (file)
index 0000000..e1ec6ff
--- /dev/null
@@ -0,0 +1,156 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.discovery;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.EQUIPMENT_ID;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.AircareApi;
+import org.openhab.binding.netatmo.internal.api.HomeApi;
+import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.WeatherApi;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain;
+import org.openhab.binding.netatmo.internal.api.dto.NAModule;
+import org.openhab.binding.netatmo.internal.config.BindingConfiguration;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link NetatmoDiscoveryService} searches for available Netatmo things
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class NetatmoDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService, DiscoveryService {
+    private static final Set<ModuleType> SKIPPED_TYPES = Set.of(ModuleType.UNKNOWN, ModuleType.ACCOUNT);
+    private static final int DISCOVER_TIMEOUT_SECONDS = 5;
+    private final Logger logger = LoggerFactory.getLogger(NetatmoDiscoveryService.class);
+    private @Nullable ApiBridgeHandler handler;
+    private @Nullable BindingConfiguration config;
+
+    public NetatmoDiscoveryService() {
+        super(ModuleType.AS_SET.stream().filter(mt -> !SKIPPED_TYPES.contains(mt)).map(mt -> mt.thingTypeUID)
+                .collect(Collectors.toSet()), DISCOVER_TIMEOUT_SECONDS);
+    }
+
+    @Override
+    public void startScan() {
+        BindingConfiguration localConf = config;
+        ApiBridgeHandler localHandler = handler;
+        if (localHandler != null && localConf != null) {
+            ThingUID apiBridgeUID = localHandler.getThing().getUID();
+            try {
+                AircareApi airCareApi = localHandler.getRestManager(AircareApi.class);
+                if (airCareApi != null) { // Search Healthy Home Coaches
+                    ListBodyResponse<NAMain> body = airCareApi.getHomeCoachData(null).getBody();
+                    if (body != null) {
+                        body.getElements().stream().forEach(homeCoach -> createThing(homeCoach, apiBridgeUID));
+                    }
+                }
+                if (localConf.readFriends) {
+                    WeatherApi weatherApi = localHandler.getRestManager(WeatherApi.class);
+                    if (weatherApi != null) { // Search favorite stations
+                        ListBodyResponse<NAMain> body = weatherApi.getStationsData(null, true).getBody();
+                        if (body != null) {
+                            body.getElements().stream().filter(NAMain::isReadOnly).forEach(station -> {
+                                ThingUID bridgeUID = createThing(station, apiBridgeUID);
+                                station.getModules().values().stream()
+                                        .forEach(module -> createThing(module, bridgeUID));
+                            });
+                        }
+                    }
+                }
+                HomeApi homeApi = localHandler.getRestManager(HomeApi.class);
+                if (homeApi != null) { // Search all the rest
+                    homeApi.getHomesData(null, null).stream().filter(h -> !h.getFeatures().isEmpty()).forEach(home -> {
+                        ThingUID homeUID = createThing(home, apiBridgeUID);
+                        home.getKnownPersons().forEach(person -> createThing(person, homeUID));
+                        home.getModules().values().stream().forEach(device -> {
+                            ModuleType deviceType = device.getType();
+                            String deviceBridge = device.getBridge();
+                            ThingUID bridgeUID = deviceBridge != null && deviceType.getBridge() != ModuleType.HOME
+                                    ? findThingUID(deviceType.getBridge(), deviceBridge, apiBridgeUID)
+                                    : deviceType.getBridge() == ModuleType.HOME ? homeUID : apiBridgeUID;
+                            createThing(device, bridgeUID);
+                        });
+                        home.getRooms().values().stream().forEach(room -> {
+                            room.getModuleIds().stream().map(id -> home.getModules().get(id))
+                                    .map(m -> m != null ? m.getType().feature : FeatureArea.NONE)
+                                    .filter(f -> FeatureArea.ENERGY.equals(f)).findAny()
+                                    .ifPresent(f -> createThing(room, homeUID));
+                        });
+                    });
+                }
+            } catch (NetatmoException e) {
+                logger.warn("Error during discovery process : {}", e.getMessage());
+            }
+        }
+    }
+
+    private ThingUID findThingUID(ModuleType thingType, String thingId, @Nullable ThingUID brigdeUID) {
+        for (ThingTypeUID supported : getSupportedThingTypes()) {
+            ThingTypeUID thingTypeUID = thingType.thingTypeUID;
+            if (supported.equals(thingTypeUID)) {
+                String id = thingId.replaceAll("[^a-zA-Z0-9_]", "");
+                return brigdeUID == null ? new ThingUID(supported, id) : new ThingUID(supported, brigdeUID, id);
+            }
+        }
+        throw new IllegalArgumentException("Unsupported device type discovered : " + thingType);
+    }
+
+    private ThingUID createThing(NAModule module, @Nullable ThingUID bridgeUID) {
+        ThingUID moduleUID = findThingUID(module.getType(), module.getId(), bridgeUID);
+        DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(moduleUID)
+                .withProperty(EQUIPMENT_ID, module.getId()).withRepresentationProperty(EQUIPMENT_ID)
+                .withLabel(module.getName() != null ? module.getName() : module.getId());
+        if (bridgeUID != null) {
+            resultBuilder.withBridge(bridgeUID);
+        }
+        thingDiscovered(resultBuilder.build());
+        return moduleUID;
+    }
+
+    @Override
+    public void setThingHandler(ThingHandler handler) {
+        if (handler instanceof ApiBridgeHandler) {
+            this.handler = (ApiBridgeHandler) handler;
+            this.config = ((ApiBridgeHandler) handler).getConfiguration();
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return handler;
+    }
+
+    @Override
+    public void deactivate() {
+        super.deactivate();
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryService.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryService.java
deleted file mode 100644 (file)
index 7b43671..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.discovery;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
-import org.openhab.binding.netatmo.internal.handler.NetatmoDataListener;
-import org.openhab.core.config.discovery.AbstractDiscoveryService;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.i18n.TranslationProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-
-import io.swagger.client.model.NAHealthyHomeCoach;
-import io.swagger.client.model.NAMain;
-import io.swagger.client.model.NAPlug;
-import io.swagger.client.model.NAStationModule;
-import io.swagger.client.model.NAWelcomeCamera;
-import io.swagger.client.model.NAWelcomeHome;
-
-/**
- * The {@link NetatmoModuleDiscoveryService} searches for available Netatmo
- * devices and modules connected to the API console
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Ing. Peter Weiss - Welcome camera implementation
- *
- */
-@NonNullByDefault
-public class NetatmoModuleDiscoveryService extends AbstractDiscoveryService implements NetatmoDataListener {
-    private static final int SEARCH_TIME = 5;
-    private final NetatmoBridgeHandler netatmoBridgeHandler;
-
-    public NetatmoModuleDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler, LocaleProvider localeProvider,
-            TranslationProvider translationProvider) {
-        super(SUPPORTED_DEVICE_THING_TYPES_UIDS, SEARCH_TIME);
-        this.netatmoBridgeHandler = netatmoBridgeHandler;
-        this.localeProvider = localeProvider;
-        this.i18nProvider = translationProvider;
-    }
-
-    @Override
-    public void activate(@Nullable Map<String, Object> configProperties) {
-        super.activate(configProperties);
-        netatmoBridgeHandler.registerDataListener(this);
-    }
-
-    @Override
-    public void deactivate() {
-        netatmoBridgeHandler.unregisterDataListener(this);
-        super.deactivate();
-    }
-
-    @Override
-    public void startScan() {
-        if (netatmoBridgeHandler.configuration.readStation) {
-            netatmoBridgeHandler.getStationsDataBody(null).ifPresent(dataBody -> {
-                nonNullList(dataBody.getDevices()).forEach(station -> {
-                    discoverWeatherStation(station);
-                });
-            });
-        }
-        if (netatmoBridgeHandler.configuration.readHealthyHomeCoach) {
-            netatmoBridgeHandler.getHomecoachDataBody(null).ifPresent(dataBody -> {
-                nonNullList(dataBody.getDevices()).forEach(homecoach -> {
-                    discoverHomeCoach(homecoach);
-                });
-            });
-        }
-        if (netatmoBridgeHandler.configuration.readThermostat) {
-            netatmoBridgeHandler.getThermostatsDataBody(null).ifPresent(dataBody -> {
-                nonNullList(dataBody.getDevices()).forEach(plug -> {
-                    discoverThermostat(plug);
-                });
-            });
-        }
-        if (netatmoBridgeHandler.configuration.readWelcome || netatmoBridgeHandler.configuration.readPresence) {
-            netatmoBridgeHandler.getWelcomeDataBody(null).ifPresent(dataBody -> {
-                nonNullList(dataBody.getHomes()).forEach(home -> {
-                    discoverWelcomeHome(home);
-                });
-            });
-        }
-    }
-
-    @Override
-    protected synchronized void stopScan() {
-        super.stopScan();
-        removeOlderResults(getTimestampOfLastScan(), netatmoBridgeHandler.getThing().getUID());
-    }
-
-    @Override
-    public void onDataRefreshed(Object data) {
-        if (!isBackgroundDiscoveryEnabled()) {
-            return;
-        }
-        if (data instanceof NAMain) {
-            discoverWeatherStation((NAMain) data);
-        } else if (data instanceof NAPlug) {
-            discoverThermostat((NAPlug) data);
-        } else if (data instanceof NAHealthyHomeCoach) {
-            discoverHomeCoach((NAHealthyHomeCoach) data);
-        } else if (data instanceof NAWelcomeHome) {
-            discoverWelcomeHome((NAWelcomeHome) data);
-        }
-    }
-
-    private void discoverThermostat(NAPlug plug) {
-        onDeviceAddedInternal(plug.getId(), null, plug.getType(), plug.getStationName(), plug.getFirmware());
-        nonNullList(plug.getModules()).forEach(thermostat -> {
-            onDeviceAddedInternal(thermostat.getId(), plug.getId(), thermostat.getType(), thermostat.getModuleName(),
-                    thermostat.getFirmware());
-        });
-    }
-
-    private void discoverHomeCoach(NAHealthyHomeCoach homecoach) {
-        onDeviceAddedInternal(homecoach.getId(), null, homecoach.getType(), homecoach.getName(),
-                homecoach.getFirmware());
-    }
-
-    private void discoverWeatherStation(NAMain station) {
-        final boolean isFavorite = station.isFavorite() != null && station.isFavorite();
-        final String weatherStationName = createWeatherStationName(station, isFavorite);
-
-        onDeviceAddedInternal(station.getId(), null, station.getType(), weatherStationName, station.getFirmware());
-        nonNullList(station.getModules()).forEach(module -> {
-            onDeviceAddedInternal(module.getId(), station.getId(), module.getType(),
-                    createWeatherModuleName(station, module, isFavorite), module.getFirmware());
-        });
-    }
-
-    private void discoverWelcomeHome(NAWelcomeHome home) {
-        // I observed that Thermostat homes are also reported here by Netatmo API
-        // So I ignore homes that have an empty list of cameras
-        List<NAWelcomeCamera> cameras = nonNullList(home.getCameras());
-        if (!cameras.isEmpty()) {
-            onDeviceAddedInternal(home.getId(), null, WELCOME_HOME_THING_TYPE.getId(), home.getName(), null);
-            // Discover Cameras
-            cameras.forEach(camera -> {
-                onDeviceAddedInternal(camera.getId(), home.getId(), camera.getType(), camera.getName(), null);
-            });
-
-            // Discover Known Persons
-            nonNullStream(home.getPersons()).filter(person -> person.getPseudo() != null).forEach(person -> {
-                onDeviceAddedInternal(person.getId(), home.getId(), WELCOME_PERSON_THING_TYPE.getId(),
-                        person.getPseudo(), null);
-            });
-        }
-    }
-
-    private void onDeviceAddedInternal(String id, @Nullable String parentId, String type, String name,
-            @Nullable Integer firmwareVersion) {
-        ThingUID thingUID = findThingUID(type, id);
-        Map<String, Object> properties = new HashMap<>();
-
-        properties.put(EQUIPMENT_ID, id);
-        if (parentId != null) {
-            properties.put(PARENT_ID, parentId);
-        }
-        if (firmwareVersion != null) {
-            properties.put(Thing.PROPERTY_VENDOR, VENDOR);
-            properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
-            properties.put(Thing.PROPERTY_MODEL_ID, type);
-            properties.put(Thing.PROPERTY_SERIAL_NUMBER, id);
-        }
-        addDiscoveredThing(thingUID, properties, name);
-    }
-
-    private void addDiscoveredThing(ThingUID thingUID, Map<String, Object> properties, String displayLabel) {
-        DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
-                .withBridge(netatmoBridgeHandler.getThing().getUID()).withLabel(displayLabel)
-                .withRepresentationProperty(EQUIPMENT_ID).build();
-
-        thingDiscovered(discoveryResult);
-    }
-
-    private ThingUID findThingUID(String thingType, String thingId) throws IllegalArgumentException {
-        for (ThingTypeUID supportedThingTypeUID : getSupportedThingTypes()) {
-            String uid = supportedThingTypeUID.getId();
-
-            if (uid.equalsIgnoreCase(thingType)) {
-                return new ThingUID(supportedThingTypeUID, netatmoBridgeHandler.getThing().getUID(),
-                        thingId.replaceAll("[^a-zA-Z0-9_]", ""));
-            }
-        }
-
-        throw new IllegalArgumentException("Unsupported device type discovered : " + thingType);
-    }
-
-    private String createWeatherStationName(NAMain station, boolean isFavorite) {
-        StringBuilder nameBuilder = new StringBuilder();
-        nameBuilder.append(localizeType(station.getType()));
-        if (station.getStationName() != null) {
-            nameBuilder.append(' ');
-            nameBuilder.append(station.getStationName());
-        }
-        if (isFavorite) {
-            nameBuilder.append(" (favorite)");
-        }
-        return nameBuilder.toString();
-    }
-
-    private String createWeatherModuleName(NAMain station, NAStationModule module, boolean isFavorite) {
-        StringBuilder nameBuilder = new StringBuilder();
-        if (module.getModuleName() != null) {
-            nameBuilder.append(module.getModuleName());
-        } else {
-            nameBuilder.append(localizeType(module.getType()));
-        }
-        if (station.getStationName() != null) {
-            nameBuilder.append(' ');
-            nameBuilder.append(station.getStationName());
-        }
-        if (isFavorite) {
-            nameBuilder.append(" (favorite)");
-        }
-        return nameBuilder.toString();
-    }
-
-    private String localizeType(String typeName) {
-        Bundle bundle = FrameworkUtil.getBundle(this.getClass());
-        @Nullable
-        String localizedType = i18nProvider.getText(bundle, "thing-type.netatmo." + typeName + ".label", typeName,
-                localeProvider.getLocale());
-        if (localizedType != null) {
-            return localizedType;
-        }
-        return typeName;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/AbstractNetatmoThingHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/AbstractNetatmoThingHandler.java
deleted file mode 100644 (file)
index c1c2974..0000000
+++ /dev/null
@@ -1,268 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.handler;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-import static org.openhab.core.library.unit.MetricPrefix.*;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-import javax.measure.Unit;
-import javax.measure.quantity.Angle;
-import javax.measure.quantity.Dimensionless;
-import javax.measure.quantity.Length;
-import javax.measure.quantity.Pressure;
-import javax.measure.quantity.Speed;
-import javax.measure.quantity.Temperature;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.channelhelper.BatteryHelper;
-import org.openhab.binding.netatmo.internal.channelhelper.RadioHelper;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.unit.SIUnits;
-import org.openhab.core.library.unit.Units;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingStatusInfo;
-import org.openhab.core.thing.binding.BaseThingHandler;
-import org.openhab.core.thing.binding.BridgeHandler;
-import org.openhab.core.thing.type.ChannelKind;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * {@link AbstractNetatmoThingHandler} is the abstract class that handles
- * common behaviors of all netatmo things
- *
- * @author Gaël L'hopital - Initial contribution OH2 version
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public abstract class AbstractNetatmoThingHandler extends BaseThingHandler {
-    // Units of measurement of the data delivered by the API
-    public static final Unit<Temperature> API_TEMPERATURE_UNIT = SIUnits.CELSIUS;
-    public static final Unit<Dimensionless> API_HUMIDITY_UNIT = Units.PERCENT;
-    public static final Unit<Pressure> API_PRESSURE_UNIT = HECTO(SIUnits.PASCAL);
-    public static final Unit<Speed> API_WIND_SPEED_UNIT = SIUnits.KILOMETRE_PER_HOUR;
-    public static final Unit<Angle> API_WIND_DIRECTION_UNIT = Units.DEGREE_ANGLE;
-    public static final Unit<Length> API_RAIN_UNIT = MILLI(SIUnits.METRE);
-    public static final Unit<Dimensionless> API_CO2_UNIT = Units.PARTS_PER_MILLION;
-    public static final Unit<Dimensionless> API_NOISE_UNIT = Units.DECIBEL;
-
-    private final Logger logger = LoggerFactory.getLogger(AbstractNetatmoThingHandler.class);
-
-    protected final TimeZoneProvider timeZoneProvider;
-    private @Nullable RadioHelper radioHelper;
-    private @Nullable BatteryHelper batteryHelper;
-    protected @Nullable Configuration config;
-    private @Nullable NetatmoBridgeHandler bridgeHandler;
-
-    AbstractNetatmoThingHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing);
-        this.timeZoneProvider = timeZoneProvider;
-    }
-
-    @Override
-    public void initialize() {
-        logger.debug("initializing handler for thing {}", getThing().getUID());
-        Bridge bridge = getBridge();
-        initializeThing(bridge != null ? bridge.getStatus() : null);
-    }
-
-    @Override
-    public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
-        logger.debug("bridgeStatusChanged {} for thing {}", bridgeStatusInfo, getThing().getUID());
-        initializeThing(bridgeStatusInfo.getStatus());
-    }
-
-    private void initializeThing(@Nullable ThingStatus bridgeStatus) {
-        Bridge bridge = getBridge();
-        BridgeHandler bridgeHandler = bridge != null ? bridge.getHandler() : null;
-        if (bridgeHandler != null && bridgeStatus != null) {
-            if (bridgeStatus == ThingStatus.ONLINE) {
-                config = getThing().getConfiguration();
-
-                String signalLevel = thing.getProperties().get(PROPERTY_SIGNAL_LEVELS);
-                radioHelper = signalLevel != null ? new RadioHelper(signalLevel) : null;
-                String batteryLevel = thing.getProperties().get(PROPERTY_BATTERY_LEVELS);
-                batteryHelper = batteryLevel != null ? new BatteryHelper(batteryLevel) : null;
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Pending parent object initialization");
-
-                initializeThing();
-            } else {
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
-            }
-        } else {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
-        }
-    }
-
-    protected abstract void initializeThing();
-
-    protected State getNAThingProperty(String channelId) {
-        Optional<State> result = getBatteryHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
-        if (result.isPresent()) {
-            return result.get();
-        }
-        result = getRadioHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
-        if (result.isPresent()) {
-            return result.get();
-        }
-        return UnDefType.UNDEF;
-    }
-
-    protected void updateChannels() {
-        if (thing.getStatus() != ThingStatus.ONLINE) {
-            return;
-        }
-
-        updateDataChannels();
-
-        triggerEventChannels();
-    }
-
-    private void updateDataChannels() {
-        getThing().getChannels().stream()
-                .filter(channel -> !ChannelKind.TRIGGER.equals(channel.getKind()) && isLinked(channel.getUID()))
-                .map(channel -> channel.getUID()).forEach(this::updateChannel);
-    }
-
-    private void updateChannel(ChannelUID channelUID) {
-        updateState(channelUID, getNAThingProperty(channelUID.getId()));
-    }
-
-    /**
-     * Triggers all event/trigger channels
-     * (when a channel is triggered, a rule can get all other information from the updated non-trigger channels)
-     */
-    private void triggerEventChannels() {
-        getThing().getChannels().stream().filter(channel -> ChannelKind.TRIGGER.equals(channel.getKind()))
-                .map(channel -> channel.getUID().getId()).forEach(this::triggerChannelIfRequired);
-    }
-
-    /**
-     * Triggers the trigger channel with the given channel id when required (when an update is available)
-     *
-     * @param channelId channel id
-     */
-    protected void triggerChannelIfRequired(String channelId) {
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-        if (command == RefreshType.REFRESH) {
-            logger.debug("Refreshing '{}'", channelUID);
-            updateChannel(channelUID);
-        }
-    }
-
-    protected Optional<NetatmoBridgeHandler> getBridgeHandler() {
-        if (bridgeHandler == null) {
-            Bridge bridge = getBridge();
-            if (bridge != null) {
-                bridgeHandler = (NetatmoBridgeHandler) bridge.getHandler();
-            }
-        }
-        NetatmoBridgeHandler handler = bridgeHandler;
-        return handler != null ? Optional.of(handler) : Optional.empty();
-    }
-
-    protected Optional<AbstractNetatmoThingHandler> findNAThing(@Nullable String searchedId) {
-        return getBridgeHandler().flatMap(handler -> handler.findNAThing(searchedId));
-    }
-
-    public boolean matchesId(@Nullable String searchedId) {
-        return searchedId != null && searchedId.equalsIgnoreCase(getId());
-    }
-
-    protected @Nullable String getId() {
-        Configuration conf = config;
-        Object equipmentId = conf != null ? conf.get(EQUIPMENT_ID) : null;
-        if (equipmentId instanceof String) {
-            return ((String) equipmentId).toLowerCase();
-        }
-        return null;
-    }
-
-    protected void updateProperties(@Nullable Integer firmware, @Nullable String modelId) {
-        Map<String, String> properties = editProperties();
-        if (firmware != null || modelId != null) {
-            properties.put(Thing.PROPERTY_VENDOR, VENDOR);
-        }
-        if (firmware != null) {
-            properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmware.toString());
-        }
-        if (modelId != null) {
-            properties.put(Thing.PROPERTY_MODEL_ID, modelId);
-        }
-        updateProperties(properties);
-    }
-
-    protected Optional<RadioHelper> getRadioHelper() {
-        RadioHelper helper = radioHelper;
-        return helper != null ? Optional.of(helper) : Optional.empty();
-    }
-
-    protected Optional<BatteryHelper> getBatteryHelper() {
-        BatteryHelper helper = batteryHelper;
-        return helper != null ? Optional.of(helper) : Optional.empty();
-    }
-
-    public void updateMeasurements() {
-    }
-
-    public void getMeasurements(@Nullable String device, @Nullable String module, String scale, List<String> types,
-            List<String> channels, Map<String, Float> channelMeasurements) {
-        Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
-        if (!handler.isPresent() || device == null) {
-            return;
-        }
-
-        if (types.size() != channels.size()) {
-            throw new IllegalArgumentException("types and channels lists are different sizes.");
-        }
-
-        List<Float> measurements = handler.get().getStationMeasureResponses(device, module, scale, types);
-        if (measurements.size() != types.size()) {
-            throw new IllegalArgumentException("types and measurements lists are different sizes.");
-        }
-
-        int i = 0;
-        for (Float measurement : measurements) {
-            channelMeasurements.put(channels.get(i++), measurement);
-        }
-    }
-
-    public void addMeasurement(List<String> channels, List<String> types, String channel, String type) {
-        if (isLinked(channel)) {
-            channels.add(channel);
-            types.add(type);
-        }
-    }
-
-    protected boolean isReachable() {
-        return true;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java
new file mode 100644 (file)
index 0000000..e0d7fe5
--- /dev/null
@@ -0,0 +1,241 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.InputStreamContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpStatus.Code;
+import org.openhab.binding.netatmo.internal.api.ApiError;
+import org.openhab.binding.netatmo.internal.api.AuthenticationApi;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.RestManager;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
+import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration;
+import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration.Credentials;
+import org.openhab.binding.netatmo.internal.config.BindingConfiguration;
+import org.openhab.binding.netatmo.internal.deserialization.NADeserializer;
+import org.openhab.binding.netatmo.internal.discovery.NetatmoDiscoveryService;
+import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.osgi.service.http.HttpService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link ApiBridgeHandler} is the handler for a Netatmo API and connects it to the framework.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ApiBridgeHandler extends BaseBridgeHandler {
+    private static final int TIMEOUT_S = 20;
+
+    private final Logger logger = LoggerFactory.getLogger(ApiBridgeHandler.class);
+    private final BindingConfiguration bindingConf;
+    private final HttpService httpService;
+    private final AuthenticationApi connectApi;
+    private final HttpClient httpClient;
+    private final NADeserializer deserializer;
+
+    private Optional<ScheduledFuture<?>> connectJob = Optional.empty();
+    private Optional<NetatmoServlet> servlet = Optional.empty();
+    private @NonNullByDefault({}) ApiHandlerConfiguration thingConf;
+
+    private Map<Class<? extends RestManager>, RestManager> managers = new HashMap<>();
+
+    public ApiBridgeHandler(Bridge bridge, HttpClient httpClient, HttpService httpService, NADeserializer deserializer,
+            BindingConfiguration configuration) {
+        super(bridge);
+        this.bindingConf = configuration;
+        this.httpService = httpService;
+        this.connectApi = new AuthenticationApi(this, scheduler);
+        this.httpClient = httpClient;
+        this.deserializer = deserializer;
+    }
+
+    @Override
+    public void initialize() {
+        logger.debug("Initializing Netatmo API bridge handler.");
+        thingConf = getConfigAs(ApiHandlerConfiguration.class);
+        updateStatus(ThingStatus.UNKNOWN);
+        scheduler.execute(() -> {
+            openConnection();
+            String webHookUrl = thingConf.webHookUrl;
+            if (webHookUrl != null && !webHookUrl.isBlank()) {
+                servlet = Optional.of(new NetatmoServlet(httpService, this, webHookUrl));
+            }
+        });
+    }
+
+    private void openConnection() {
+        try {
+            Credentials credentials = thingConf.getCredentials();
+            logger.debug("Connecting to Netatmo API.");
+            try {
+                connectApi.authenticate(credentials, bindingConf.features);
+                updateStatus(ThingStatus.ONLINE);
+                getThing().getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler).filter(Objects::nonNull)
+                        .map(CommonInterface.class::cast).forEach(CommonInterface::expireData);
+            } catch (NetatmoException e) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+                prepareReconnection();
+            }
+        } catch (NetatmoException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
+        }
+    }
+
+    private void prepareReconnection() {
+        connectApi.disconnect();
+        freeConnectJob();
+        connectJob = Optional
+                .of(scheduler.schedule(() -> openConnection(), thingConf.reconnectInterval, TimeUnit.SECONDS));
+    }
+
+    private void freeConnectJob() {
+        connectJob.ifPresent(j -> j.cancel(true));
+        connectJob = Optional.empty();
+    }
+
+    @Override
+    public void dispose() {
+        logger.debug("Shutting down Netatmo API bridge handler.");
+        servlet.ifPresent(servlet -> servlet.dispose());
+        servlet = Optional.empty();
+        connectApi.dispose();
+        freeConnectJob();
+        super.dispose();
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        logger.debug("Netatmo Bridge is read-only and does not handle commands");
+    }
+
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return Set.of(NetatmoDiscoveryService.class);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends RestManager> @Nullable T getRestManager(Class<T> clazz) {
+        if (!managers.containsKey(clazz)) {
+            try {
+                Constructor<T> constructor = clazz.getConstructor(getClass());
+                T instance = constructor.newInstance(this);
+                Set<Scope> expected = instance.getRequiredScopes();
+                if (connectApi.matchesScopes(expected)) {
+                    managers.put(clazz, instance);
+                } else {
+                    logger.info("Unable to instantiate {}, expected scope {} is not active", clazz, expected);
+                }
+            } catch (SecurityException | ReflectiveOperationException e) {
+                logger.warn("Error invoking RestManager constructor for class {} : {}", clazz, e.getMessage());
+            }
+        }
+        return (T) managers.get(clazz);
+    }
+
+    public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nullable String payload,
+            @Nullable String contentType, int retryCount) throws NetatmoException {
+        try {
+            logger.trace("executeUri {}  {} ", method.toString(), uri);
+
+            Request request = httpClient.newRequest(uri).method(method).timeout(TIMEOUT_S, TimeUnit.SECONDS);
+
+            String auth = connectApi.getAuthorization();
+            if (auth != null) {
+                request.header(HttpHeader.AUTHORIZATION, auth);
+            }
+
+            if (payload != null && contentType != null
+                    && (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method))) {
+                InputStream stream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
+                try (InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider(stream)) {
+                    request.content(inputStreamContentProvider, contentType);
+                }
+            }
+
+            ContentResponse response = request.send();
+
+            Code statusCode = HttpStatus.getCode(response.getStatus());
+            String responseBody = new String(response.getContent(), StandardCharsets.UTF_8);
+            logger.trace("executeUri returned : code {} body {}", statusCode, responseBody);
+
+            if (statusCode != Code.OK) {
+                ApiError error = deserializer.deserialize(ApiError.class, responseBody);
+                throw new NetatmoException(error);
+            }
+            return deserializer.deserialize(clazz, responseBody);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+            throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
+        } catch (TimeoutException | ExecutionException e) {
+            if (retryCount > 0) {
+                logger.debug("Request timedout, retry counter : {}", retryCount);
+                return executeUri(uri, method, clazz, payload, contentType, retryCount - 1);
+            }
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/request-time-out");
+            prepareReconnection();
+            throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
+        }
+    }
+
+    public BindingConfiguration getConfiguration() {
+        return bindingConf;
+    }
+
+    public Optional<NetatmoServlet> getServlet() {
+        return servlet;
+    }
+
+    public NADeserializer getDeserializer() {
+        return deserializer;
+    }
+
+    public boolean isConnected() {
+        return connectApi.isConnected();
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java
new file mode 100644 (file)
index 0000000..aa519e2
--- /dev/null
@@ -0,0 +1,220 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+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.RefreshCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.RestCapability;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BridgeHandler;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.thing.type.ChannelKind;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+
+/**
+ * {@link CommonInterface} defines common methods of AccountHandler and NAThingHandlers used by Capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public interface CommonInterface {
+    Thing getThing();
+
+    ThingBuilder editThing();
+
+    CapabilityMap getCapabilities();
+
+    Logger getLogger();
+
+    ScheduledExecutorService getScheduler();
+
+    boolean isLinked(ChannelUID channelUID);
+
+    void updateState(ChannelUID channelUID, State state);
+
+    void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail,
+            @Nullable String thingStatusReason);
+
+    void triggerChannel(String channelID, String event);
+
+    void updateThing(Thing thing);
+
+    @Nullable
+    Bridge getBridge();
+
+    default @Nullable CommonInterface getBridgeHandler() {
+        Bridge bridge = getBridge();
+        return bridge != null && bridge.getHandler() instanceof DeviceHandler ? (DeviceHandler) bridge.getHandler()
+                : null;
+    }
+
+    default @Nullable ApiBridgeHandler getAccountHandler() {
+        Bridge bridge = getBridge();
+        BridgeHandler bridgeHandler = null;
+        if (bridge != null) {
+            bridgeHandler = bridge.getHandler();
+            while (bridgeHandler != null && !(bridgeHandler instanceof ApiBridgeHandler)) {
+                bridge = ((CommonInterface) bridgeHandler).getBridge();
+                bridgeHandler = bridge != null ? bridge.getHandler() : null;
+            }
+        }
+        return (ApiBridgeHandler) bridgeHandler;
+    }
+
+    default @Nullable String getBridgeId() {
+        CommonInterface bridge = getBridgeHandler();
+        return bridge != null ? bridge.getId() : null;
+    }
+
+    default void expireData() {
+        getCapabilities().values().forEach(cap -> cap.expireData());
+    }
+
+    default String getId() {
+        return (String) getThing().getConfiguration().get("id");
+    }
+
+    default Stream<Channel> getActiveChannels() {
+        return getThing().getChannels().stream()
+                .filter(channel -> ChannelKind.STATE.equals(channel.getKind()) && isLinked(channel.getUID()));
+    }
+
+    default Optional<CommonInterface> getHomeHandler() {
+        CommonInterface bridgeHandler = getBridgeHandler();
+        if (bridgeHandler != null) {
+            return bridgeHandler.getCapabilities().get(HomeCapability.class).isPresent() ? Optional.of(bridgeHandler)
+                    : Optional.empty();
+        }
+        return Optional.empty();
+    }
+
+    default List<CommonInterface> getActiveChildren() {
+        Thing thing = getThing();
+        if (thing instanceof Bridge) {
+            return ((Bridge) thing).getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler)
+                    .filter(Objects::nonNull).map(CommonInterface.class::cast).collect(Collectors.toList());
+        }
+        return List.of();
+    }
+
+    default <T extends RestCapability<?>> Optional<T> getHomeCapability(Class<T> clazz) {
+        return getHomeHandler().map(handler -> handler.getCapabilities().get(clazz)).orElse(Optional.empty());
+    }
+
+    default void setNewData(NAObject newData) {
+        String finalReason = null;
+        for (Capability cap : getCapabilities().values()) {
+            String thingStatusReason = cap.setNewData(newData);
+            if (thingStatusReason != null) {
+                finalReason = thingStatusReason;
+            }
+        }
+        if (!newData.isIgnoredForThingUpdate()) {
+            setThingStatus(finalReason == null ? ThingStatus.ONLINE : ThingStatus.OFFLINE, ThingStatusDetail.NONE,
+                    finalReason);
+        }
+    }
+
+    default void commonHandleCommand(ChannelUID channelUID, Command command) {
+        if (ThingStatus.ONLINE.equals(getThing().getStatus())) {
+            if (command == RefreshType.REFRESH) {
+                expireData();
+                return;
+            }
+            String channelName = channelUID.getIdWithoutGroup();
+            getCapabilities().values().forEach(cap -> cap.handleCommand(channelName, command));
+        } else {
+            getLogger().debug("Command {}, on channel {} dropped - thing is not ONLINE", command, channelUID);
+        }
+    }
+
+    default void proceedWithUpdate() {
+        updateReadings().forEach(dataSet -> setNewData(dataSet));
+    }
+
+    default List<NAObject> updateReadings() {
+        List<NAObject> result = new ArrayList<>();
+        getCapabilities().values().forEach(cap -> result.addAll(cap.updateReadings()));
+        getActiveChildren().forEach(child -> result.addAll(child.updateReadings()));
+        return result;
+    }
+
+    default void commonInitialize() {
+        Bridge bridge = getBridge();
+        if (bridge == null || bridge.getHandler() == null) {
+            setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, null);
+        } else if (!ThingStatus.ONLINE.equals(bridge.getStatus())) {
+            setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
+            removeRefreshCapability();
+        } else {
+            setThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null);
+            setRefreshCapability();
+            getCapabilities().values().forEach(cap -> cap.initialize());
+            getScheduler().schedule(() -> {
+                CommonInterface bridgeHandler = getBridgeHandler();
+                if (bridgeHandler != null) {
+                    bridgeHandler.expireData();
+                }
+            }, 1, TimeUnit.SECONDS);
+        }
+    }
+
+    default void setRefreshCapability() {
+        ModuleType moduleType = ModuleType.from(getThing().getThingTypeUID());
+        if (ModuleType.ACCOUNT.equals(moduleType.getBridge())) {
+            NAThingConfiguration config = getThing().getConfiguration().as(NAThingConfiguration.class);
+            getCapabilities().put(new RefreshCapability(this, getScheduler(), config.refreshInterval));
+        }
+    }
+
+    default void removeRefreshCapability() {
+        Capability refreshCap = getCapabilities().remove(RefreshCapability.class);
+        if (refreshCap != null) {
+            refreshCap.dispose();
+        }
+    }
+
+    default void commonDispose() {
+        getCapabilities().values().forEach(Capability::dispose);
+    }
+
+    default void removeChannels(List<Channel> channels) {
+        ThingBuilder builder = editThing().withoutChannels(channels);
+        updateThing(builder.build());
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/DeviceHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/DeviceHandler.java
new file mode 100644 (file)
index 0000000..6638f52
--- /dev/null
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.builder.BridgeBuilder;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link DeviceHandler} is the base class for all Netatmo bridges
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class DeviceHandler extends BaseBridgeHandler implements CommonInterface {
+    private final Logger logger = LoggerFactory.getLogger(DeviceHandler.class);
+    private CapabilityMap capabilities = new CapabilityMap();
+
+    public DeviceHandler(Bridge bridge) {
+        super(bridge);
+    }
+
+    @Override
+    public void initialize() {
+        logger.debug("Initializing handler for bridge {}", getThing().getUID());
+        commonInitialize();
+    }
+
+    @Override
+    public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+        logger.debug("bridgeStatusChanged for bridge {} to {}", getThing().getUID(), bridgeStatusInfo);
+        commonInitialize();
+    }
+
+    @Override
+    public void dispose() {
+        commonDispose();
+        super.dispose();
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        commonHandleCommand(channelUID, command);
+    }
+
+    @Override
+    public void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail,
+            @Nullable String thingStatusReason) {
+        updateStatus(thingStatus, thingStatusDetail, thingStatusReason);
+    }
+
+    @Override
+    public CapabilityMap getCapabilities() {
+        return capabilities;
+    }
+
+    @Override
+    public BridgeBuilder editThing() {
+        return super.editThing();
+    }
+
+    @Override
+    public void updateThing(Thing thing) {
+        super.updateThing(thing);
+    }
+
+    @Override
+    public void updateState(ChannelUID channelUID, State state) {
+        super.updateState(channelUID, state);
+    }
+
+    @Override
+    public boolean isLinked(ChannelUID channelUID) {
+        return super.isLinked(channelUID);
+    }
+
+    @Override
+    public @Nullable Bridge getBridge() {
+        return super.getBridge();
+    }
+
+    @Override
+    public void triggerChannel(String channelID, String event) {
+        super.triggerChannel(channelID, event);
+    }
+
+    @Override
+    public Logger getLogger() {
+        return logger;
+    }
+
+    @Override
+    public ScheduledExecutorService getScheduler() {
+        return scheduler;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ModuleHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ModuleHandler.java
new file mode 100644 (file)
index 0000000..5e5d5db
--- /dev/null
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link ModuleHandler} is the base class for all Netatmo things
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ModuleHandler extends BaseThingHandler implements CommonInterface {
+    private final Logger logger = LoggerFactory.getLogger(ModuleHandler.class);
+    private CapabilityMap capabilities = new CapabilityMap();
+
+    public ModuleHandler(Thing thing) {
+        super(thing);
+    }
+
+    @Override
+    public void initialize() {
+        logger.debug("Initializing handler for thing {}", getThing().getUID());
+        commonInitialize();
+    }
+
+    @Override
+    public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+        logger.debug("bridgeStatusChanged for thing {} to {}", getThing().getUID(), bridgeStatusInfo);
+        commonInitialize();
+    }
+
+    @Override
+    public void dispose() {
+        commonDispose();
+        super.dispose();
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        commonHandleCommand(channelUID, command);
+    }
+
+    @Override
+    public void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail,
+            @Nullable String thingStatusReason) {
+        updateStatus(thingStatus, thingStatusDetail, thingStatusReason);
+    }
+
+    @Override
+    public CapabilityMap getCapabilities() {
+        return capabilities;
+    }
+
+    @Override
+    public ThingBuilder editThing() {
+        return super.editThing();
+    }
+
+    @Override
+    public void updateThing(Thing thing) {
+        super.updateThing(thing);
+    }
+
+    @Override
+    public void updateState(ChannelUID channelUID, State state) {
+        super.updateState(channelUID, state);
+    }
+
+    @Override
+    public boolean isLinked(ChannelUID channelUID) {
+        return super.isLinked(channelUID);
+    }
+
+    @Override
+    public @Nullable Bridge getBridge() {
+        return super.getBridge();
+    }
+
+    @Override
+    public void triggerChannel(String channelID, String event) {
+        super.triggerChannel(channelID, event);
+    }
+
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        List<Class<? extends ThingHandlerService>> result = new ArrayList<>();
+        capabilities.values().forEach(cap -> result.addAll(cap.getServices()));
+        return result;
+    }
+
+    @Override
+    public Logger getLogger() {
+        return logger;
+    }
+
+    @Override
+    public ScheduledExecutorService getScheduler() {
+        return scheduler;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoBridgeHandler.java
deleted file mode 100644 (file)
index de6d8fe..0000000
+++ /dev/null
@@ -1,408 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.handler;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
-
-import org.apache.oltu.oauth2.client.OAuthClient;
-import org.apache.oltu.oauth2.client.URLConnectionClient;
-import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
-import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
-import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
-import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
-import org.apache.oltu.oauth2.common.message.types.GrantType;
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.config.NetatmoBridgeConfiguration;
-import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
-import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEventPerson;
-import org.openhab.binding.netatmo.internal.webhook.WelcomeWebHookServlet;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.Channel;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.binding.BaseBridgeHandler;
-import org.openhab.core.types.Command;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.swagger.client.ApiClient;
-import io.swagger.client.ApiException;
-import io.swagger.client.api.HealthyhomecoachApi;
-import io.swagger.client.api.PartnerApi;
-import io.swagger.client.api.StationApi;
-import io.swagger.client.api.ThermostatApi;
-import io.swagger.client.api.WelcomeApi;
-import io.swagger.client.auth.Authentication;
-import io.swagger.client.auth.OAuth;
-import io.swagger.client.model.NAHealthyHomeCoachDataBody;
-import io.swagger.client.model.NAMeasureBodyElem;
-import io.swagger.client.model.NAStationDataBody;
-import io.swagger.client.model.NAThermostatDataBody;
-import io.swagger.client.model.NAWelcomeHomeData;
-
-/**
- * {@link NetatmoBridgeHandler} is the handler for a Netatmo API and connects it
- * to the framework. The devices and modules uses the
- * {@link NetatmoBridgeHandler} to request informations about their status
- *
- * @author Gaël L'hopital - Initial contribution OH2 version
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class NetatmoBridgeHandler extends BaseBridgeHandler {
-    private final Logger logger = LoggerFactory.getLogger(NetatmoBridgeHandler.class);
-
-    public NetatmoBridgeConfiguration configuration = new NetatmoBridgeConfiguration();
-    private @Nullable ScheduledFuture<?> refreshJob;
-    private @Nullable APICreator apiCreator;
-    private @Nullable WelcomeWebHookServlet webHookServlet;
-    private List<NetatmoDataListener> dataListeners = new CopyOnWriteArrayList<>();
-
-    private static class APICreator {
-
-        private final ApiClient apiClient;
-        private final Map<Class<?>, Object> apiMap;
-
-        private APICreator(ApiClient apiClient) {
-            super();
-            this.apiClient = apiClient;
-            apiMap = new HashMap<>();
-        }
-
-        @SuppressWarnings("unchecked")
-        public <T> T getAPI(Class<T> apiClass) {
-            T api = (T) apiMap.get(apiClass);
-            if (api == null) {
-                try {
-                    api = apiClass.getDeclaredConstructor(ApiClient.class).newInstance(apiClient);
-                } catch (InstantiationException | IllegalAccessException | InvocationTargetException
-                        | NoSuchMethodException e) {
-                    throw new RuntimeException("Error on executing API class constructor!", e);
-                }
-                apiMap.put(apiClass, api);
-            }
-            return api;
-        }
-    }
-
-    public NetatmoBridgeHandler(Bridge bridge, @Nullable WelcomeWebHookServlet webHookServlet) {
-        super(bridge);
-        this.webHookServlet = webHookServlet;
-    }
-
-    @Override
-    public void initialize() {
-        logger.debug("Initializing Netatmo API bridge handler.");
-
-        configuration = getConfigAs(NetatmoBridgeConfiguration.class);
-        scheduleTokenInitAndRefresh();
-    }
-
-    private void connectionSucceed() {
-        updateStatus(ThingStatus.ONLINE);
-        WelcomeWebHookServlet servlet = webHookServlet;
-        String webHookURI = getWebHookURI();
-        if (servlet != null && webHookURI != null) {
-            getWelcomeApi().ifPresent(api -> {
-                servlet.activate(this);
-                logger.debug("Setting up Netatmo Welcome WebHook");
-                api.addwebhook(webHookURI, WEBHOOK_APP);
-            });
-        }
-    }
-
-    private void scheduleTokenInitAndRefresh() {
-        refreshJob = scheduler.scheduleWithFixedDelay(() -> {
-            logger.debug("Initializing API Connection and scheduling token refresh every {}s",
-                    configuration.reconnectInterval);
-            try {
-                initializeApiClient();
-                // I use a connection to Netatmo API using PartnerAPI to ensure that API is reachable
-                getPartnerApi().partnerdevices();
-                connectionSucceed();
-            } catch (ApiException e) {
-                switch (e.getCode()) {
-                    case 404: // If no partner station has been associated - likely to happen - we'll have this
-                              // error
-                              // but it means connection to API is OK
-                        connectionSucceed();
-                        break;
-                    case 403: // Forbidden Access maybe too many requests ? Let's wait next cycle
-                        logger.warn("Error 403 while connecting to Netatmo API, will retry in {} s",
-                                configuration.reconnectInterval);
-                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
-                                "Netatmo Access Forbidden, will retry in " + configuration.reconnectInterval
-                                        + " seconds.");
-                        break;
-                    default:
-                        if (logger.isDebugEnabled()) {
-                            // we also attach the stack trace
-                            logger.error("Unable to connect Netatmo API : {}", e.getMessage(), e);
-                        } else {
-                            logger.error("Unable to connect Netatmo API : {}", e.getMessage());
-                        }
-                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
-                                "Unable to connect Netatmo API : " + e.getLocalizedMessage());
-                }
-            } catch (RuntimeException e) {
-                if (logger.isDebugEnabled()) {
-                    logger.warn("Unable to connect Netatmo API : {}", e.getMessage(), e);
-                } else {
-                    logger.warn("Unable to connect Netatmo API : {}", e.getMessage());
-                }
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
-                        "Netatmo Access Failed, will retry in " + configuration.reconnectInterval + " seconds.");
-            }
-            // We'll do this every x seconds to guaranty token refresh
-        }, 2, configuration.reconnectInterval, TimeUnit.SECONDS);
-    }
-
-    private void initializeApiClient() {
-        try {
-            ApiClient apiClient = new ApiClient();
-
-            OAuthClientRequest oAuthRequest = OAuthClientRequest.tokenLocation("https://api.netatmo.net/oauth2/token")
-                    .setClientId(configuration.clientId).setClientSecret(configuration.clientSecret)
-                    .setUsername(configuration.username).setPassword(configuration.password).setScope(getApiScope())
-                    .setGrantType(GrantType.PASSWORD).buildBodyMessage();
-
-            OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
-
-            OAuthJSONAccessTokenResponse accessTokenResponse = oAuthClient.accessToken(oAuthRequest,
-                    OAuthJSONAccessTokenResponse.class);
-            String accessToken = accessTokenResponse.getAccessToken();
-
-            for (Authentication authentication : apiClient.getAuthentications().values()) {
-                if (authentication instanceof OAuth) {
-                    ((OAuth) authentication).setAccessToken(accessToken);
-                }
-            }
-
-            apiCreator = new APICreator(apiClient);
-        } catch (OAuthSystemException | OAuthProblemException e) {
-            throw new RuntimeException("Error on trying to get an access token!", e);
-        }
-    }
-
-    private String getApiScope() {
-        List<String> scopes = new ArrayList<>();
-
-        if (configuration.readStation) {
-            scopes.add("read_station");
-        }
-
-        if (configuration.readThermostat) {
-            scopes.add("read_thermostat");
-            scopes.add("write_thermostat");
-        }
-
-        if (configuration.readHealthyHomeCoach) {
-            scopes.add("read_homecoach");
-        }
-
-        if (configuration.readWelcome) {
-            scopes.add("read_camera");
-            scopes.add("access_camera");
-            scopes.add("write_camera");
-        }
-
-        if (configuration.readPresence) {
-            scopes.add("read_presence");
-            scopes.add("access_presence");
-        }
-
-        return String.join(" ", scopes);
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-        logger.debug("Netatmo Bridge is read-only and does not handle commands");
-    }
-
-    public @Nullable PartnerApi getPartnerApi() {
-        return apiCreator != null ? apiCreator.getAPI(PartnerApi.class) : null;
-    }
-
-    public Optional<StationApi> getStationApi() {
-        return apiCreator != null ? Optional.of(apiCreator.getAPI(StationApi.class)) : Optional.empty();
-    }
-
-    public Optional<HealthyhomecoachApi> getHomeCoachApi() {
-        return apiCreator != null ? Optional.of(apiCreator.getAPI(HealthyhomecoachApi.class)) : Optional.empty();
-    }
-
-    public Optional<ThermostatApi> getThermostatApi() {
-        return apiCreator != null ? Optional.of(apiCreator.getAPI(ThermostatApi.class)) : Optional.empty();
-    }
-
-    public Optional<WelcomeApi> getWelcomeApi() {
-        return apiCreator != null ? Optional.of(apiCreator.getAPI(WelcomeApi.class)) : Optional.empty();
-    }
-
-    @Override
-    public void dispose() {
-        logger.debug("Running dispose()");
-
-        WelcomeWebHookServlet servlet = webHookServlet;
-        if (servlet != null && getWebHookURI() != null) {
-            getWelcomeApi().ifPresent(api -> {
-                logger.debug("Releasing Netatmo Welcome WebHook");
-                servlet.deactivate();
-                api.dropwebhook(WEBHOOK_APP);
-            });
-        }
-
-        ScheduledFuture<?> job = refreshJob;
-        if (job != null) {
-            job.cancel(true);
-            refreshJob = null;
-        }
-    }
-
-    public Optional<NAStationDataBody> getStationsDataBody(@Nullable String equipmentId) {
-        Optional<NAStationDataBody> data = getStationApi().map(api -> api.getstationsdata(equipmentId, true).getBody());
-        updateStatus(ThingStatus.ONLINE);
-        return data;
-    }
-
-    public List<Float> getStationMeasureResponses(String equipmentId, @Nullable String moduleId, String scale,
-            List<String> types) {
-        List<NAMeasureBodyElem> data = getStationApi()
-                .map(api -> api.getmeasure(equipmentId, scale, types, moduleId, null, "last", 1, true, false).getBody())
-                .orElse(null);
-        updateStatus(ThingStatus.ONLINE);
-        NAMeasureBodyElem element = data != null && !data.isEmpty() ? data.get(0) : null;
-        return element != null ? element.getValue().get(0) : Collections.emptyList();
-    }
-
-    public Optional<NAHealthyHomeCoachDataBody> getHomecoachDataBody(@Nullable String equipmentId) {
-        Optional<NAHealthyHomeCoachDataBody> data = getHomeCoachApi()
-                .map(api -> api.gethomecoachsdata(equipmentId).getBody());
-        updateStatus(ThingStatus.ONLINE);
-        return data;
-    }
-
-    public Optional<NAThermostatDataBody> getThermostatsDataBody(@Nullable String equipmentId) {
-        Optional<NAThermostatDataBody> data = getThermostatApi()
-                .map(api -> api.getthermostatsdata(equipmentId).getBody());
-        updateStatus(ThingStatus.ONLINE);
-        return data;
-    }
-
-    public Optional<NAWelcomeHomeData> getWelcomeDataBody(@Nullable String homeId) {
-        Optional<NAWelcomeHomeData> data = getWelcomeApi().map(api -> api.gethomedata(homeId, null).getBody());
-        updateStatus(ThingStatus.ONLINE);
-        return data;
-    }
-
-    /**
-     * Returns the Url of the picture
-     *
-     * @return Url of the picture or UnDefType.UNDEF
-     */
-    public String getPictureUrl(@Nullable String id, @Nullable String key) {
-        StringBuilder ret = new StringBuilder();
-        if (id != null && key != null) {
-            ret.append(WELCOME_PICTURE_URL).append("?").append(WELCOME_PICTURE_IMAGEID).append("=").append(id)
-                    .append("&").append(WELCOME_PICTURE_KEY).append("=").append(key);
-        }
-        return ret.toString();
-    }
-
-    public Optional<AbstractNetatmoThingHandler> findNAThing(@Nullable String searchedId) {
-        List<Thing> things = getThing().getThings();
-        Stream<AbstractNetatmoThingHandler> naHandlers = things.stream().map(Thing::getHandler)
-                .filter(AbstractNetatmoThingHandler.class::isInstance).map(AbstractNetatmoThingHandler.class::cast)
-                .filter(handler -> handler.matchesId(searchedId));
-        return naHandlers.findAny();
-    }
-
-    public void webHookEvent(NAWebhookCameraEvent event) {
-        // This currently the only known event type but I suspect usage can grow in the future...
-        if (event.getAppType() == NAWebhookCameraEvent.AppTypeEnum.CAMERA) {
-            Set<AbstractNetatmoThingHandler> modules = new HashSet<>();
-            if (WELCOME_EVENTS.contains(event.getEventType()) || PRESENCE_EVENTS.contains(event.getEventType())) {
-                String cameraId = event.getCameraId();
-                if (cameraId != null) {
-                    Optional<AbstractNetatmoThingHandler> camera = findNAThing(cameraId);
-                    camera.ifPresent(modules::add);
-                }
-            }
-            if (HOME_EVENTS.contains(event.getEventType())) {
-                String homeId = event.getHomeId();
-                if (homeId != null) {
-                    Optional<AbstractNetatmoThingHandler> home = findNAThing(homeId);
-                    home.ifPresent(modules::add);
-                }
-            }
-            if (PERSON_EVENTS.contains(event.getEventType())) {
-                List<NAWebhookCameraEventPerson> persons = event.getPersons();
-                persons.forEach(person -> {
-                    String personId = person.getId();
-                    if (personId != null) {
-                        Optional<AbstractNetatmoThingHandler> personHandler = findNAThing(personId);
-                        personHandler.ifPresent(modules::add);
-                    }
-                });
-            }
-            modules.forEach(module -> {
-                Channel channel = module.getThing().getChannel(CHANNEL_WELCOME_HOME_EVENT);
-                if (channel != null) {
-                    triggerChannel(channel.getUID(), event.getEventType().toString());
-                }
-            });
-        }
-    }
-
-    private @Nullable String getWebHookURI() {
-        String webHookURI = null;
-        WelcomeWebHookServlet webHookServlet = this.webHookServlet;
-        if (configuration.webHookUrl != null && (configuration.readWelcome || configuration.readPresence)
-                && webHookServlet != null) {
-            webHookURI = configuration.webHookUrl + webHookServlet.getPath();
-        }
-        return webHookURI;
-    }
-
-    public boolean registerDataListener(NetatmoDataListener dataListener) {
-        return dataListeners.add(dataListener);
-    }
-
-    public boolean unregisterDataListener(NetatmoDataListener dataListener) {
-        return dataListeners.remove(dataListener);
-    }
-
-    public void checkForNewThings(Object data) {
-        for (NetatmoDataListener dataListener : dataListeners) {
-            dataListener.onDataRefreshed(data);
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDataListener.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDataListener.java
deleted file mode 100644 (file)
index bf42b2f..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.handler;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * The {@link NetatmoDataListener} allows receiving notification when any netatmo device thing handler
- * is getting refreshed data from the netatmo server.
- *
- * @author Laurent Garnier - Initial contribution
- */
-@NonNullByDefault
-public interface NetatmoDataListener {
-
-    /**
-     * This method is called just after the thing handler fetched new data from the netatmo server.
-     *
-     * @param data the retrieved data.
-     */
-    public void onDataRefreshed(Object data);
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDeviceHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDeviceHandler.java
deleted file mode 100644 (file)
index dbd9f7a..0000000
+++ /dev/null
@@ -1,257 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.handler;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.math.BigDecimal;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
-import org.openhab.binding.netatmo.internal.RefreshStrategy;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.PointType;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.swagger.client.ApiException;
-import io.swagger.client.model.NAPlace;
-
-/**
- * {@link NetatmoDeviceHandler} is the handler for a given
- * device accessed through the Netatmo Bridge
- *
- * @author Gaël L'hopital - Initial contribution
- */
-@NonNullByDefault
-public abstract class NetatmoDeviceHandler<DEVICE> extends AbstractNetatmoThingHandler {
-
-    private static final int MIN_REFRESH_INTERVAL = 2000;
-    private static final int DEFAULT_REFRESH_INTERVAL = 300000;
-
-    private final Logger logger = LoggerFactory.getLogger(NetatmoDeviceHandler.class);
-    private final Object updateLock = new Object();
-    private @Nullable ScheduledFuture<?> refreshJob;
-    private @Nullable RefreshStrategy refreshStrategy;
-    private @Nullable DEVICE device;
-    protected Map<String, Object> childs = new ConcurrentHashMap<>();
-
-    public NetatmoDeviceHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected void initializeThing() {
-        defineRefreshInterval();
-        updateStatus(ThingStatus.ONLINE);
-        scheduleRefreshJob();
-    }
-
-    private void scheduleRefreshJob() {
-        RefreshStrategy strategy = refreshStrategy;
-        if (strategy == null) {
-            return;
-        }
-        long delay = strategy.nextRunDelayInS();
-        logger.debug("Scheduling update channel thread in {} s", delay);
-        refreshJob = scheduler.schedule(() -> {
-            updateChannels(false);
-            ScheduledFuture<?> job = refreshJob;
-            if (job != null) {
-                job.cancel(false);
-                refreshJob = null;
-            }
-            scheduleRefreshJob();
-        }, delay, TimeUnit.SECONDS);
-    }
-
-    @Override
-    public void dispose() {
-        logger.debug("Running dispose()");
-        ScheduledFuture<?> job = refreshJob;
-        if (job != null) {
-            job.cancel(true);
-            refreshJob = null;
-        }
-    }
-
-    protected abstract Optional<DEVICE> updateReadings();
-
-    protected void updateProperties(DEVICE deviceData) {
-    }
-
-    @Override
-    protected void updateChannels() {
-        updateChannels(true);
-    }
-
-    private void updateChannels(boolean requireDefinedRefreshInterval) {
-        // Avoid concurrent data readings
-        synchronized (updateLock) {
-            RefreshStrategy strategy = refreshStrategy;
-            if (strategy != null) {
-                logger.debug("Data aged of {} s", strategy.dataAge() / 1000);
-                boolean dataOutdated = (requireDefinedRefreshInterval && strategy.isSearchingRefreshInterval()) ? false
-                        : strategy.isDataOutdated();
-                if (dataOutdated) {
-                    logger.debug("Trying to update channels on device {}", getId());
-                    childs.clear();
-
-                    Optional<DEVICE> newDeviceReading = Optional.empty();
-                    try {
-                        newDeviceReading = updateReadings();
-                    } catch (ApiException e) {
-                        if (logger.isDebugEnabled()) {
-                            // we also attach the stack trace
-                            logger.error("Unable to connect Netatmo API : {}", e.getMessage(), e);
-                        } else {
-                            logger.error("Unable to connect Netatmo API : {}", e.getMessage());
-                        }
-                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
-                                "Unable to connect Netatmo API : " + e.getLocalizedMessage());
-                    }
-                    if (newDeviceReading.isPresent()) {
-                        logger.debug("Successfully updated device {} readings! Now updating channels", getId());
-                        DEVICE theDevice = newDeviceReading.get();
-                        this.device = theDevice;
-                        updateStatus(isReachable() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
-                        updateProperties(theDevice);
-                        getDataTimestamp().ifPresent(dataTimeStamp -> {
-                            strategy.setDataTimeStamp(dataTimeStamp, timeZoneProvider.getTimeZone());
-                        });
-                        getRadioHelper().ifPresent(helper -> helper.setModule(theDevice));
-                        getBridgeHandler().ifPresent(handler -> {
-                            handler.checkForNewThings(theDevice);
-                        });
-                    } else {
-                        logger.debug("Failed to update device {} readings! Skip updating channels", getId());
-                    }
-                    // Be sure that all channels for the modules will be updated with refreshed data
-                    childs.forEach((childId, moduleData) -> {
-                        findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> {
-                            naChildModule.setRefreshRequired(true);
-                        });
-                    });
-                } else {
-                    logger.debug("Data still valid for device {}", getId());
-                }
-                super.updateChannels();
-                updateChildModules();
-            }
-        }
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        try {
-            Optional<DEVICE> dev = getDevice();
-            switch (channelId) {
-                case CHANNEL_LAST_STATUS_STORE:
-                    if (dev.isPresent()) {
-                        Method getLastStatusStore = dev.get().getClass().getMethod("getLastStatusStore");
-                        Integer lastStatusStore = (Integer) getLastStatusStore.invoke(dev.get());
-                        return ChannelTypeUtils.toDateTimeType(lastStatusStore, timeZoneProvider.getTimeZone());
-                    } else {
-                        return UnDefType.UNDEF;
-                    }
-                case CHANNEL_LOCATION:
-                    if (dev.isPresent()) {
-                        Method getPlace = dev.get().getClass().getMethod("getPlace");
-                        NAPlace place = (NAPlace) getPlace.invoke(dev.get());
-                        PointType point = new PointType(new DecimalType(place.getLocation().get(1)),
-                                new DecimalType(place.getLocation().get(0)));
-                        if (place.getAltitude() != null) {
-                            point.setAltitude(new DecimalType(place.getAltitude()));
-                        }
-                        return point;
-                    } else {
-                        return UnDefType.UNDEF;
-                    }
-            }
-        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
-            logger.debug("The device has no method to access {} property ", channelId);
-            return UnDefType.NULL;
-        }
-
-        return super.getNAThingProperty(channelId);
-    }
-
-    private void updateChildModules() {
-        logger.debug("Updating child modules of {}", getId());
-        childs.forEach((childId, moduleData) -> {
-            findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> {
-                logger.debug("Updating child module {}", naChildModule.getId());
-                naChildModule.updateChannels(moduleData);
-            });
-        });
-    }
-
-    /*
-     * Sets the refresh rate of the device depending whether it's a property
-     * of the thing or if it's defined by configuration
-     */
-    private void defineRefreshInterval() {
-        BigDecimal dataValidityPeriod;
-        if (thing.getProperties().containsKey(PROPERTY_REFRESH_PERIOD)) {
-            String refreshPeriodProperty = thing.getProperties().get(PROPERTY_REFRESH_PERIOD);
-            if ("auto".equalsIgnoreCase(refreshPeriodProperty)) {
-                dataValidityPeriod = new BigDecimal(-1);
-            } else {
-                dataValidityPeriod = new BigDecimal(refreshPeriodProperty);
-            }
-        } else {
-            Configuration conf = config;
-            Object interval = conf != null ? conf.get(REFRESH_INTERVAL) : null;
-            if (interval instanceof BigDecimal) {
-                dataValidityPeriod = (BigDecimal) interval;
-                if (dataValidityPeriod.intValue() < MIN_REFRESH_INTERVAL) {
-                    logger.info(
-                            "Refresh interval setting is too small for thing {}, {} ms is considered as refresh interval.",
-                            thing.getUID(), MIN_REFRESH_INTERVAL);
-                    dataValidityPeriod = new BigDecimal(MIN_REFRESH_INTERVAL);
-                }
-            } else {
-                dataValidityPeriod = new BigDecimal(DEFAULT_REFRESH_INTERVAL);
-            }
-        }
-        refreshStrategy = new RefreshStrategy(dataValidityPeriod.intValue());
-    }
-
-    protected abstract Optional<Integer> getDataTimestamp();
-
-    public void expireData() {
-        RefreshStrategy strategy = refreshStrategy;
-        if (strategy != null) {
-            strategy.expireData();
-        }
-    }
-
-    protected Optional<DEVICE> getDevice() {
-        return Optional.ofNullable(device);
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoModuleHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoModuleHandler.java
deleted file mode 100644 (file)
index 9c9f7b1..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.handler;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Optional;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * {@link NetatmoModuleHandler} is the handler for a given
- * module device accessed through the Netatmo Device
- *
- * @author Gaël L'hopital - Initial contribution
- */
-@NonNullByDefault
-public class NetatmoModuleHandler<MODULE> extends AbstractNetatmoThingHandler {
-    private final Logger logger = LoggerFactory.getLogger(NetatmoModuleHandler.class);
-    private @Nullable ScheduledFuture<?> refreshJob;
-    private @Nullable MODULE module;
-    private boolean refreshRequired;
-
-    protected NetatmoModuleHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected void initializeThing() {
-        refreshJob = scheduler.schedule(() -> {
-            requestParentRefresh();
-        }, 5, TimeUnit.SECONDS);
-    }
-
-    @Override
-    public void dispose() {
-        ScheduledFuture<?> job = refreshJob;
-        if (job != null) {
-            job.cancel(true);
-            refreshJob = null;
-        }
-    }
-
-    protected @Nullable String getParentId() {
-        Configuration conf = config;
-        Object parentId = conf != null ? conf.get(PARENT_ID) : null;
-        if (parentId instanceof String) {
-            return ((String) parentId).toLowerCase();
-        }
-        return null;
-    }
-
-    public boolean childOf(AbstractNetatmoThingHandler naThingHandler) {
-        return naThingHandler.matchesId(getParentId());
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        try {
-            Optional<MODULE> mod = getModule();
-            if (channelId.equalsIgnoreCase(CHANNEL_LAST_MESSAGE) && mod.isPresent()) {
-                Method getLastMessage = mod.get().getClass().getMethod("getLastMessage");
-                Integer lastMessage = (Integer) getLastMessage.invoke(mod.get());
-                return ChannelTypeUtils.toDateTimeType(lastMessage, timeZoneProvider.getTimeZone());
-            }
-        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
-                | InvocationTargetException e) {
-            logger.debug("The module has no method to access {} property ", channelId);
-            return UnDefType.NULL;
-        }
-
-        return super.getNAThingProperty(channelId);
-    }
-
-    protected void updateChannels(Object module) {
-        MODULE theModule = (MODULE) module;
-        setModule(theModule);
-        updateStatus(isReachable() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
-        getRadioHelper().ifPresent(helper -> helper.setModule(module));
-        getBatteryHelper().ifPresent(helper -> helper.setModule(module));
-        updateProperties(theModule);
-        super.updateChannels();
-    }
-
-    protected void invalidateParentCacheAndRefresh() {
-        setRefreshRequired(true);
-        // Leave a bit of time to Netatmo Server to get in sync with new values sent
-        scheduler.schedule(() -> {
-            invalidateParentCache();
-            requestParentRefresh();
-        }, 2, TimeUnit.SECONDS);
-    }
-
-    protected void requestParentRefresh() {
-        setRefreshRequired(true);
-        findNAThing(getParentId()).ifPresent(AbstractNetatmoThingHandler::updateChannels);
-    }
-
-    private void invalidateParentCache() {
-        findNAThing(getParentId()).map(NetatmoDeviceHandler.class::cast).ifPresent(NetatmoDeviceHandler::expireData);
-    }
-
-    protected void updateProperties(MODULE moduleData) {
-    }
-
-    protected boolean isRefreshRequired() {
-        return refreshRequired;
-    }
-
-    protected void setRefreshRequired(boolean refreshRequired) {
-        this.refreshRequired = refreshRequired;
-    }
-
-    protected Optional<MODULE> getModule() {
-        return Optional.ofNullable(module);
-    }
-
-    public void setModule(MODULE module) {
-        this.module = module;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AirCareCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AirCareCapability.java
new file mode 100644 (file)
index 0000000..9e55e7c
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2022 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.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.AircareApi;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link AirCareCapability} give the ability to read home coach api
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class AirCareCapability extends RestCapability<AircareApi> {
+    private final Logger logger = LoggerFactory.getLogger(AirCareCapability.class);
+
+    public AirCareCapability(CommonInterface handler) {
+        super(handler, AircareApi.class);
+    }
+
+    @Override
+    protected List<NAObject> updateReadings(AircareApi api) {
+        try {
+            return List.of(api.getHomeCoach(handler.getId()));
+        } catch (NetatmoException e) {
+            logger.warn("Error retrieving home-coach data '{}' : {}", handler.getId(), e.getMessage());
+        }
+        return List.of();
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java
new file mode 100644 (file)
index 0000000..8f6246d
--- /dev/null
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2010-2022 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 static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.AlimentationStatus;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SdCardStatus;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+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.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.StateOption;
+
+/**
+ * {@link CameraCapability} give to handle Welcome Camera specifics
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class CameraCapability extends HomeSecurityThingCapability {
+    private final CameraChannelHelper cameraHelper;
+    private final ChannelUID personChannelUID;
+
+    protected @Nullable String localUrl;
+
+    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.cameraHelper = (CameraChannelHelper) channelHelpers.stream().filter(c -> c instanceof CameraChannelHelper)
+                .findFirst().orElseThrow(() -> new IllegalArgumentException(
+                        "CameraCapability must find a CameraChannelHelper, please file a bug report."));
+    }
+
+    @Override
+    public void updateHomeStatusModule(HomeStatusModule newData) {
+        super.updateHomeStatusModule(newData);
+        String vpnUrl = newData.getVpnUrl();
+        if (vpnUrl != null) {
+            localUrl = newData.isLocal() ? securityCapability.map(cap -> cap.ping(vpnUrl)).orElse(null) : null;
+            cameraHelper.setUrls(vpnUrl, localUrl);
+            eventHelper.setUrls(vpnUrl, localUrl);
+        }
+        if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus())
+                || !AlimentationStatus.ALIM_CORRECT_POWER.equals(newData.getAlimStatus())) {
+            statusReason = String.format("%s, %s", newData.getSdStatus(), newData.getAlimStatus());
+        }
+    }
+
+    @Override
+    public void handleCommand(String channelName, Command command) {
+        if (command instanceof OnOffType && CHANNEL_MONITORING.equals(channelName)) {
+            securityCapability.ifPresent(cap -> cap.changeStatus(localUrl, OnOffType.ON.equals(command)));
+        } else {
+            super.handleCommand(channelName, command);
+        }
+    }
+
+    @Override
+    protected void beforeNewData() {
+        super.beforeNewData();
+        homeCapability.ifPresent(cap -> {
+            NAObjectMap<HomeDataPerson> persons = cap.getPersons();
+            descriptionProvider.setStateOptions(personChannelUID, persons.values().stream()
+                    .map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList()));
+        });
+    }
+
+    @Override
+    public List<NAObject> updateReadings() {
+        List<NAObject> result = new ArrayList<>();
+        securityCapability.ifPresent(cap -> {
+            Collection<HomeEvent> events = cap.getCameraEvents(handler.getId());
+            if (!events.isEmpty()) {
+                result.add(events.iterator().next());
+            }
+        });
+        return result;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/Capability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/Capability.java
new file mode 100644 (file)
index 0000000..a2ef48a
--- /dev/null
@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2010-2022 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 static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.VENDOR;
+import static org.openhab.core.thing.Thing.*;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.dto.Device;
+import org.openhab.binding.netatmo.internal.api.dto.Event;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain;
+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.openhab.core.thing.Thing;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link Capability} is the base class for all inherited capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class Capability {
+
+    protected final Thing thing;
+    protected final CommonInterface handler;
+    protected final ModuleType moduleType;
+
+    protected boolean firstLaunch;
+    protected Map<String, String> properties = Map.of();
+    protected @Nullable String statusReason;
+
+    Capability(CommonInterface handler) {
+        this.handler = handler;
+        this.thing = handler.getThing();
+        this.moduleType = ModuleType.from(thing.getThingTypeUID());
+    }
+
+    public final @Nullable String setNewData(NAObject newData) {
+        beforeNewData();
+        if (newData instanceof HomeData) {
+            updateHomeData((HomeData) newData);
+        }
+        if (newData instanceof HomeStatus) {
+            updateHomeStatus((HomeStatus) newData);
+        }
+        if (newData instanceof HomeStatusModule) {
+            updateHomeStatusModule((HomeStatusModule) newData);
+        }
+        if (newData instanceof Event) {
+            updateEvent((Event) newData);
+        }
+        if (newData instanceof HomeEvent) {
+            updateHomeEvent((HomeEvent) newData);
+        }
+        if (newData instanceof NAThing) {
+            updateNAThing((NAThing) newData);
+        }
+        if (newData instanceof NAMain) {
+            updateNAMain((NAMain) newData);
+        }
+        if (newData instanceof Device) {
+            updateNADevice((Device) newData);
+        }
+        afterNewData(newData);
+        return statusReason;
+    }
+
+    protected void beforeNewData() {
+        properties = new HashMap<>(thing.getProperties());
+        firstLaunch = properties.isEmpty();
+        if (firstLaunch && !moduleType.isLogical()) {
+            properties.put(PROPERTY_VENDOR, VENDOR);
+            properties.put(PROPERTY_MODEL_ID, moduleType.name());
+        }
+        statusReason = null;
+    }
+
+    protected void afterNewData(@Nullable NAObject newData) {
+        if (!properties.equals(thing.getProperties())) {
+            thing.setProperties(properties);
+        }
+    }
+
+    protected void updateNAThing(NAThing newData) {
+        String firmware = newData.getFirmware();
+        if (firmware != null && !firmware.isBlank()) {
+            properties.put(PROPERTY_FIRMWARE_VERSION, firmware);
+        }
+        if (!newData.isReachable()) {
+            statusReason = "@text/device-not-connected";
+        }
+    }
+
+    protected void updateNAMain(NAMain newData) {
+        // do nothing by default, can be overridden by subclasses
+    }
+
+    protected void updateHomeEvent(HomeEvent newData) {
+        // do nothing by default, can be overridden by subclasses
+    }
+
+    protected void updateHomeStatus(HomeStatus newData) {
+        // do nothing by default, can be overridden by subclasses
+    }
+
+    protected void updateHomeData(HomeData newData) {
+        // do nothing by default, can be overridden by subclasses
+    }
+
+    protected void updateEvent(Event newData) {
+        // do nothing by default, can be overridden by subclasses
+    }
+
+    protected void updateNADevice(Device newData) {
+        // do nothing by default, can be overridden by subclasses
+    }
+
+    public void initialize() {
+        // do nothing by default, can be overridden by subclasses
+    }
+
+    public void expireData() {
+        if (!handler.getCapabilities().containsKey(RefreshCapability.class)) {
+            CommonInterface bridgeHandler = handler.getBridgeHandler();
+            if (bridgeHandler != null) {
+                bridgeHandler.expireData();
+            }
+        }
+    }
+
+    public void dispose() {
+        // do nothing by default, can be overridden by subclasses
+    }
+
+    public void updateHomeStatusModule(HomeStatusModule newData) {
+        // do nothing by default, can be overridden by subclasses
+    }
+
+    public void handleCommand(String channelName, Command command) {
+        // do nothing by default, can be overridden by subclasses
+    }
+
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return List.of();
+    }
+
+    public List<NAObject> updateReadings() {
+        return List.of();
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CapabilityMap.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CapabilityMap.java
new file mode 100644 (file)
index 0000000..3cbe3f1
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2022 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.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * {@link CapabilityMap} is a specialized Map designed to store capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class CapabilityMap extends ConcurrentHashMap<Class<?>, Capability> {
+    private static final long serialVersionUID = -3043492242108419801L;
+
+    public void put(Capability capability) {
+        Class<? extends Capability> clazz = capability.getClass();
+        if (super.get(clazz) == null) {
+            super.put(clazz, capability);
+        }
+    }
+
+    public <T extends Capability> Optional<T> get(Class<T> clazz) {
+        @SuppressWarnings("unchecked")
+        T cap = (T) super.get(clazz);
+        return Optional.ofNullable(cap);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/ChannelHelperCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/ChannelHelperCapability.java
new file mode 100644 (file)
index 0000000..3e2fcf5
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2010-2022 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.util.List;
+
+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.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.State;
+
+/**
+ * {@link ChannelHelperCapability} give the capability to dispatch incoming data across the channel helpers.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ChannelHelperCapability extends Capability {
+    private final List<ChannelHelper> channelHelpers;
+
+    public ChannelHelperCapability(CommonInterface handler, List<ChannelHelper> channelHelpers) {
+        super(handler);
+        this.channelHelpers = channelHelpers;
+    }
+
+    @Override
+    public void afterNewData(@Nullable NAObject newData) {
+        super.afterNewData(newData);
+        channelHelpers.forEach(helper -> helper.setNewData(newData));
+        handler.getActiveChannels().forEach(channel -> {
+            ChannelUID channelUID = channel.getUID();
+            String channelID = channelUID.getIdWithoutGroup();
+            String groupId = channelUID.getGroupId();
+            Configuration channelConfig = channel.getConfiguration();
+            for (ChannelHelper helper : channelHelpers) {
+                State state = helper.getChannelState(channelID, groupId, channelConfig);
+                if (state != null) {
+                    handler.updateState(channelUID, state);
+                    break;
+                }
+            }
+        });
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/DeviceCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/DeviceCapability.java
new file mode 100644 (file)
index 0000000..2b7db3d
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2022 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 static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+
+/**
+ * The {@link DeviceCapability} takes care of handling properties for netatmo devices
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class DeviceCapability extends Capability {
+    private static final int DATA_AGE_LIMIT_S = 3600;
+
+    public DeviceCapability(CommonInterface handler) {
+        super(handler);
+    }
+
+    @Override
+    protected void updateNAMain(NAMain newData) {
+        if (firstLaunch) {
+            newData.getPlace().ifPresent(place -> {
+                place.getCity().map(city -> properties.put(PROPERTY_CITY, city));
+                place.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country));
+                place.getTimezone().map(tz -> properties.put(PROPERTY_TIMEZONE, tz));
+            });
+        }
+        if (!newData.hasFreshData(DATA_AGE_LIMIT_S)) {
+            statusReason = "@text/data-over-limit";
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java
new file mode 100644 (file)
index 0000000..328eb26
--- /dev/null
@@ -0,0 +1,155 @@
+/**
+ * Copyright (c) 2010-2022 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 static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import java.time.ZonedDateTime;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.EnergyApi;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataRoom;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.Room;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.StateOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link EnergyCapability} is the base class for handler able to handle energy features
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class EnergyCapability extends RestCapability<EnergyApi> {
+    private final Logger logger = LoggerFactory.getLogger(EnergyCapability.class);
+
+    private int setPointDefaultDuration = -1;
+    private final NetatmoDescriptionProvider descriptionProvider;
+
+    EnergyCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) {
+        super(handler, EnergyApi.class);
+        this.descriptionProvider = descriptionProvider;
+    }
+
+    @Override
+    protected void updateHomeData(HomeData homeData) {
+        NAObjectMap<HomeDataRoom> rooms = homeData.getRooms();
+        NAObjectMap<HomeDataModule> modules = homeData.getModules();
+        handler.getActiveChildren().forEach(handler -> {
+            HomeDataRoom roomData = rooms.get(handler.getId());
+            if (roomData != null) {
+                roomData.setIgnoredForThingUpdate(true);
+                handler.setNewData(roomData);
+            }
+            HomeDataModule moduleData = modules.get(handler.getId());
+            if (moduleData != null) {
+                moduleData.setIgnoredForThingUpdate(true);
+                handler.setNewData(moduleData);
+            }
+        });
+        descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), GROUP_ENERGY, CHANNEL_PLANNING),
+                homeData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName()))
+                        .collect(Collectors.toList()));
+        setPointDefaultDuration = homeData.getThermSetpointDefaultDuration();
+    }
+
+    @Override
+    protected void updateHomeStatus(HomeStatus homeStatus) {
+        NAObjectMap<Room> rooms = homeStatus.getRooms();
+        NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
+        handler.getActiveChildren().forEach(handler -> {
+            Room roomData = rooms.get(handler.getId());
+            if (roomData != null) {
+                handler.setNewData(roomData);
+            }
+            HomeStatusModule data = modules.get(handler.getId());
+            if (data != null) {
+                handler.setNewData(data);
+            }
+        });
+    }
+
+    public int getSetpointDefaultDuration() {
+        return setPointDefaultDuration;
+    }
+
+    public void setRoomThermMode(String roomId, SetpointMode targetMode) {
+        getApi().ifPresent(api -> {
+            try {
+                api.setThermpoint(handler.getId(), roomId, targetMode,
+                        targetMode == SetpointMode.MAX ? setpointEndTimeFromNow(setPointDefaultDuration) : 0, 0);
+                handler.expireData();
+            } catch (NetatmoException e) {
+                logger.warn("Error setting room thermostat mode '{}' : {}", targetMode, e.getMessage());
+            }
+        });
+    }
+
+    public void setRoomThermTemp(String roomId, double temperature, long endtime, SetpointMode mode) {
+        getApi().ifPresent(api -> {
+            try {
+                api.setThermpoint(handler.getId(), roomId, mode, endtime, temperature);
+                handler.expireData();
+            } catch (NetatmoException e) {
+                logger.warn("Error setting room thermostat mode '{}' : {}", mode, e.getMessage());
+            }
+        });
+    }
+
+    public void setRoomThermTemp(String roomId, double temperature) {
+        setRoomThermTemp(roomId, temperature, setpointEndTimeFromNow(setPointDefaultDuration), SetpointMode.MANUAL);
+    }
+
+    @Override
+    public void handleCommand(String channelName, Command command) {
+        getApi().ifPresent(api -> {
+            try {
+                switch (channelName) {
+                    case CHANNEL_PLANNING:
+                        api.switchSchedule(handler.getId(), command.toString());
+                        break;
+                    case CHANNEL_SETPOINT_MODE:
+                        SetpointMode targetMode = SetpointMode.valueOf(command.toString());
+                        if (targetMode == SetpointMode.MANUAL) {
+                            logger.info("Switch to 'Manual' is done by setting a setpoint temp, command ignored");
+                            return;
+                        }
+                        api.setThermMode(handler.getId(), targetMode.apiDescriptor);
+                        break;
+                }
+                handler.expireData();
+            } catch (NetatmoException e) {
+                logger.warn("Error handling command '{}' : {}", command, e.getMessage());
+            } catch (IllegalArgumentException e) {
+                logger.warn("Command '{}' sent to channel '{}' is not a valid setpoint mode.", command, channelName);
+            }
+        });
+    }
+
+    private static long setpointEndTimeFromNow(int duration_min) {
+        return ZonedDateTime.now().plusMinutes(duration_min).toEpochSecond();
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EventCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EventCapability.java
new file mode 100644 (file)
index 0000000..a72abc4
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2022 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.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet;
+
+/**
+ * {@link EventCapability} is the base class for handlers
+ * subject to receive event notifications. This class registers to webhookservlet so
+ * it can be notified when an event arrives.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class EventCapability extends Capability {
+    private Optional<NetatmoServlet> servlet = Optional.empty();
+
+    public EventCapability(CommonInterface handler) {
+        super(handler);
+    }
+
+    @Override
+    public void initialize() {
+        ApiBridgeHandler accountHandler = handler.getAccountHandler();
+        if (accountHandler != null) {
+            servlet = accountHandler.getServlet();
+            servlet.ifPresent(s -> s.registerDataListener(handler.getId(), this));
+        }
+    }
+
+    @Override
+    public void dispose() {
+        servlet.ifPresent(s -> s.unregisterDataListener(this));
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java
new file mode 100644 (file)
index 0000000..0e001ba
--- /dev/null
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2010-2022 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 static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.HomeApi;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
+import org.openhab.binding.netatmo.internal.api.dto.Location;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link HomeCapability} is the base class for handler able to manage persons and modules
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HomeCapability extends RestCapability<HomeApi> {
+    private final Logger logger = LoggerFactory.getLogger(HomeCapability.class);
+
+    private final NetatmoDescriptionProvider descriptionProvider;
+
+    private NAObjectMap<HomeDataPerson> persons = new NAObjectMap<>();
+    private NAObjectMap<HomeDataModule> modules = new NAObjectMap<>();
+
+    private Set<FeatureArea> featuresArea = Set.of();
+
+    public HomeCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) {
+        super(handler, HomeApi.class);
+        this.descriptionProvider = descriptionProvider;
+    }
+
+    @Override
+    protected void updateHomeData(HomeData home) {
+        featuresArea = home.getFeatures();
+        if (hasFeature(FeatureArea.SECURITY) && !handler.getCapabilities().containsKey(SecurityCapability.class)) {
+            handler.getCapabilities().put(new SecurityCapability(handler));
+        }
+        if (hasFeature(FeatureArea.ENERGY) && !handler.getCapabilities().containsKey(EnergyCapability.class)) {
+            handler.getCapabilities().put(new EnergyCapability(handler, descriptionProvider));
+        }
+        if (firstLaunch) {
+            home.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country));
+            home.getTimezone().map(tz -> properties.put(PROPERTY_TIMEZONE, tz));
+            properties.put(GROUP_LOCATION, ((Location) home).getLocation().toString());
+            properties.put(PROPERTY_FEATURE, featuresArea.stream().map(f -> f.name()).collect(Collectors.joining(",")));
+        }
+    }
+
+    @Override
+    protected void afterNewData(@Nullable NAObject newData) {
+        super.afterNewData(newData);
+        if (firstLaunch && !hasFeature(FeatureArea.SECURITY)) {
+            handler.removeChannels(thing.getChannelsOfGroup(GROUP_SECURITY));
+        }
+        if (firstLaunch && !hasFeature(FeatureArea.ENERGY)) {
+            handler.removeChannels(thing.getChannelsOfGroup(GROUP_ENERGY));
+        }
+    }
+
+    private boolean hasFeature(FeatureArea seeked) {
+        return featuresArea.contains(seeked);
+    }
+
+    public NAObjectMap<HomeDataPerson> getPersons() {
+        return persons;
+    }
+
+    public NAObjectMap<HomeDataModule> getModules() {
+        return modules;
+    }
+
+    @Override
+    protected List<NAObject> updateReadings(HomeApi api) {
+        List<NAObject> result = new ArrayList<>();
+        try {
+            HomeData homeData = api.getHomeData(handler.getId());
+            if (homeData != null) {
+                result.add(homeData);
+                persons = homeData.getPersons();
+                modules = homeData.getModules();
+            }
+            HomeStatus homeStatus = api.getHomeStatus(handler.getId());
+            if (homeStatus != null) {
+                result.add(homeStatus);
+            }
+        } catch (NetatmoException e) {
+            logger.warn("Error gettting Home informations : {}", e.getMessage());
+        }
+        return result;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeSecurityThingCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeSecurityThingCapability.java
new file mode 100644 (file)
index 0000000..0b808fa
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2022 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.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.EventChannelHelper;
+import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+
+/**
+ * {@link HomeSecurityThingCapability} is the ancestor of capabilities hosted by a security home
+ * e.g. person and camera capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HomeSecurityThingCapability extends Capability {
+    protected final NetatmoDescriptionProvider descriptionProvider;
+    protected final EventChannelHelper eventHelper;
+
+    protected Optional<SecurityCapability> securityCapability = Optional.empty();
+    protected Optional<HomeCapability> homeCapability = Optional.empty();
+
+    public HomeSecurityThingCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
+            List<ChannelHelper> channelHelpers) {
+        super(handler);
+        this.descriptionProvider = descriptionProvider;
+        this.eventHelper = (EventChannelHelper) channelHelpers.stream().filter(c -> c instanceof EventChannelHelper)
+                .findFirst().orElseThrow(() -> new IllegalArgumentException(
+                        "HomeSecurityThingCapability must find an EventChannelHelper, please file a bug report."));
+        eventHelper.setModuleType(moduleType);
+    }
+
+    @Override
+    public void initialize() {
+        super.initialize();
+        securityCapability = handler.getHomeCapability(SecurityCapability.class);
+        homeCapability = handler.getHomeCapability(HomeCapability.class);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/MeasureCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/MeasureCapability.java
new file mode 100644 (file)
index 0000000..39b3123
--- /dev/null
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2010-2022 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
+
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.WeatherApi;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.config.MeasureConfiguration;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.MeasuresChannelHelper;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link MeasureCapability} is the base class for handler able to handle user defined measures
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class MeasureCapability extends RestCapability<WeatherApi> {
+    private final Logger logger = LoggerFactory.getLogger(MeasureCapability.class);
+    private final Map<String, State> measures = new HashMap<>();
+
+    public MeasureCapability(CommonInterface handler, List<ChannelHelper> helpers) {
+        super(handler, WeatherApi.class);
+        MeasuresChannelHelper measureChannelHelper = (MeasuresChannelHelper) helpers.stream()
+                .filter(c -> c instanceof MeasuresChannelHelper).findFirst()
+                .orElseThrow(() -> new IllegalArgumentException(
+                        "MeasureCapability must find a MeasuresChannelHelper, please file a bug report."));
+        measureChannelHelper.setMeasures(measures);
+    }
+
+    @Override
+    public List<NAObject> updateReadings(WeatherApi api) {
+        String bridgeId = handler.getBridgeId();
+        String deviceId = bridgeId != null ? bridgeId : handler.getId();
+        String moduleId = bridgeId != null ? handler.getId() : null;
+        updateMeasures(api, deviceId, moduleId);
+        return List.of();
+    }
+
+    private void updateMeasures(WeatherApi api, String deviceId, @Nullable String moduleId) {
+        measures.clear();
+        handler.getActiveChannels().filter(channel -> !channel.getConfiguration().getProperties().isEmpty())
+                .forEach(channel -> {
+                    ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
+                    if (channelTypeUID != null) {
+                        MeasureConfiguration measureDef = channel.getConfiguration().as(MeasureConfiguration.class);
+                        String descriptor = channelTypeUID.getId().split("-")[0];
+                        try {
+                            Object result = measureDef.limit.isBlank()
+                                    ? api.getMeasures(deviceId, moduleId, measureDef.period, descriptor)
+                                    : api.getMeasures(deviceId, moduleId, measureDef.period, descriptor,
+                                            measureDef.limit);
+                            MeasureClass.AS_SET.stream().filter(mc -> mc.apiDescriptor.equals(descriptor)).findFirst()
+                                    .ifPresent(mc -> {
+                                        State state = result instanceof ZonedDateTime
+                                                ? toDateTimeType((ZonedDateTime) result)
+                                                : result instanceof Double ? toQuantityType((Double) result, mc)
+                                                        : UnDefType.UNDEF;
+                                        measures.put(channel.getUID().getIdWithoutGroup(), state);
+                                    });
+                        } catch (NetatmoException e) {
+                            logger.warn("Error getting measures for channel {}, check configuration",
+                                    channel.getLabel());
+                        }
+                    }
+                });
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PersonCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PersonCapability.java
new file mode 100644 (file)
index 0000000..3ef4d11
--- /dev/null
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2010-2022 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 static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.dto.Event;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+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.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.StateOption;
+
+/**
+ * {@link PersonCapability} gives the ability to handle Person specifics
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class PersonCapability extends HomeSecurityThingCapability {
+    private final ChannelUID cameraChannelUID;
+    private @Nullable ZonedDateTime lastEventTime;
+
+    public PersonCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
+            List<ChannelHelper> channelHelpers) {
+        super(handler, descriptionProvider, channelHelpers);
+        this.cameraChannelUID = new ChannelUID(thing.getUID(), GROUP_PERSON_EVENT, CHANNEL_EVENT_CAMERA_ID);
+    }
+
+    @Override
+    protected void beforeNewData() {
+        super.beforeNewData();
+        homeCapability.ifPresent(cap -> {
+            Stream<HomeDataModule> cameras = cap.getModules().values().stream()
+                    .filter(module -> module.getType() == ModuleType.WELCOME);
+            descriptionProvider.setStateOptions(cameraChannelUID,
+                    cameras.map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList()));
+        });
+    }
+
+    @Override
+    public void handleCommand(String channelName, Command command) {
+        if ((command instanceof OnOffType) && CHANNEL_PERSON_AT_HOME.equals(channelName)) {
+            securityCapability.ifPresent(cap -> cap.setPersonAway(handler.getId(), OnOffType.OFF.equals(command)));
+        }
+    }
+
+    @Override
+    public void updateEvent(Event event) {
+        super.updateEvent(event);
+        EventType eventType = event.getEventType();
+        ZonedDateTime localLast = lastEventTime;
+        ZonedDateTime eventTime = event.getTime();
+        if ((localLast != null && !eventTime.isAfter(localLast)) || !eventType.appliesOn(ModuleType.PERSON)) {
+            return; // ignore incoming events if they are deprecated
+        }
+        lastEventTime = eventTime;
+        handler.triggerChannel(CHANNEL_HOME_EVENT,
+                event.getSubTypeDescription().map(st -> st.name()).orElse(event.getEventType().name()));
+    }
+
+    @Override
+    public List<NAObject> updateReadings() {
+        List<NAObject> result = new ArrayList<>();
+        securityCapability.ifPresent(cap -> {
+            Collection<HomeEvent> events = cap.getPersonEvents(handler.getId());
+            if (!events.isEmpty()) {
+                result.add(events.iterator().next());
+            }
+        });
+        return result;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PresenceCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PresenceCapability.java
new file mode 100644 (file)
index 0000000..d800dcc
--- /dev/null
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2010-2022 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 static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.CHANNEL_FLOODLIGHT;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
+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.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link PresenceCapability} give to handle Presence Camera specifics
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class PresenceCapability extends CameraCapability {
+    private final Logger logger = LoggerFactory.getLogger(PresenceCapability.class);
+
+    public PresenceCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
+            List<ChannelHelper> channelHelpers) {
+        super(handler, descriptionProvider, channelHelpers);
+    }
+
+    @Override
+    public void handleCommand(String channelName, Command command) {
+        if (CHANNEL_FLOODLIGHT.equals(channelName)) {
+            if (command instanceof OnOffType) {
+                changeFloodlightMode(command == OnOffType.ON ? FloodLightMode.ON : FloodLightMode.OFF);
+                return;
+            } else if (command instanceof StringType) {
+                try {
+                    FloodLightMode mode = FloodLightMode.valueOf(command.toString());
+                    changeFloodlightMode(mode);
+                } catch (IllegalArgumentException e) {
+                    logger.info("Incorrect command '{}' received for channel '{}'", command, channelName);
+                }
+                return;
+            }
+        }
+        super.handleCommand(channelName, command);
+    }
+
+    private void changeFloodlightMode(FloodLightMode mode) {
+        securityCapability.ifPresent(cap -> cap.changeFloodlightMode(localUrl, mode));
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RefreshCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RefreshCapability.java
new file mode 100644 (file)
index 0000000..87c8d43
--- /dev/null
@@ -0,0 +1,124 @@
+/**
+ * Copyright (c) 2010-2022 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 static java.time.temporal.ChronoUnit.*;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Optional;
+import java.util.concurrent.ScheduledExecutorService;
+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.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
+ *
+ * @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);
+
+    private final Logger logger = LoggerFactory.getLogger(RefreshCapability.class);
+    private final ScheduledExecutorService scheduler;
+
+    private Duration dataValidity;
+    private Instant dataTimeStamp = Instant.now();
+    private Instant dataTimeStamp0 = Instant.MIN;
+    private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
+    private final boolean refreshConfigured;
+
+    public RefreshCapability(CommonInterface handler, ScheduledExecutorService scheduler, int refreshInterval) {
+        super(handler);
+        this.scheduler = scheduler;
+        this.dataValidity = Duration.ofSeconds(Math.max(0, refreshInterval));
+        this.refreshConfigured = !probing();
+        freeJobAndReschedule(2);
+    }
+
+    @Override
+    public void dispose() {
+        freeJobAndReschedule(0);
+        super.dispose();
+    }
+
+    @Override
+    public void expireData() {
+        dataTimeStamp = Instant.now().minus(dataValidity);
+        freeJobAndReschedule(1);
+    }
+
+    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("Module is not ONLINE; special refresh interval is used");
+            delay = OFFLINE_INTERVAL.toSeconds();
+            if (probing()) {
+                dataTimeStamp0 = Instant.MIN;
+            }
+        } else if (refreshConfigured) {
+            delay = dataValidity.getSeconds();
+        } else {
+            delay = (probing() ? PROBING_INTERVAL : dataValidity.minus(dataAge()).plus(DEFAULT_DELAY)).toSeconds();
+        }
+        delay = delay < 2 ? PROBING_INTERVAL.toSeconds() : delay;
+        logger.debug("Module refreshed, next one in {} s", delay);
+        freeJobAndReschedule(delay);
+    }
+
+    @Override
+    protected void updateNAThing(NAThing newData) {
+        super.updateNAThing(newData);
+        newData.getLastSeen().ifPresent(timestamp -> {
+            Instant tsInstant = timestamp.toInstant();
+            if (probing()) {
+                if (Instant.MIN.equals(dataTimeStamp0)) {
+                    dataTimeStamp0 = tsInstant;
+                    logger.debug("First data timestamp is {}", dataTimeStamp0);
+                } else if (tsInstant.isAfter(dataTimeStamp0)) {
+                    dataValidity = Duration.between(dataTimeStamp0, tsInstant);
+                    logger.debug("Data validity period identified to be {}", dataValidity);
+                } else {
+                    logger.debug("Data validity period not yet found - data timestamp unchanged");
+                }
+            }
+            dataTimeStamp = tsInstant;
+        });
+    }
+
+    private void freeJobAndReschedule(long delay) {
+        refreshJob.ifPresent(job -> job.cancel(false));
+        refreshJob = delay == 0 ? Optional.empty()
+                : Optional.of(scheduler.schedule(() -> proceedWithUpdate(), delay, TimeUnit.SECONDS));
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RestCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RestCapability.java
new file mode 100644 (file)
index 0000000..2ac29b2
--- /dev/null
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) 2010-2022 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.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.RestManager;
+import org.openhab.binding.netatmo.internal.api.dto.Device;
+import org.openhab.binding.netatmo.internal.api.dto.Module;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+
+/**
+ * The {@link RestCapability} is the base class for handler capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public abstract class RestCapability<T extends RestManager> extends DeviceCapability {
+    private Optional<T> api = Optional.empty();
+    private Class<T> restManagerClass;
+
+    RestCapability(CommonInterface handler, Class<T> restManagerClazz) {
+        super(handler);
+        this.restManagerClass = restManagerClazz;
+    }
+
+    @Override
+    protected void updateNADevice(Device newData) {
+        super.updateNADevice(newData);
+        NAObjectMap<Module> modules = newData.getModules();
+        handler.getActiveChildren().forEach(child -> {
+            Module childData = modules.get(child.getId());
+            if (childData != null) {
+                child.setNewData(childData);
+            }
+        });
+    }
+
+    @Override
+    public final List<NAObject> updateReadings() {
+        List<NAObject> result = new ArrayList<>();
+        getApi().ifPresent(api -> result.addAll(updateReadings(api)));
+        return result;
+    }
+
+    protected List<NAObject> updateReadings(T api) {
+        return List.of();
+    }
+
+    protected Optional<T> getApi() {
+        if (api.isEmpty()) {
+            ApiBridgeHandler bridgeApi = handler.getAccountHandler();
+            if (bridgeApi != null) {
+                api = Optional.ofNullable(bridgeApi.getRestManager(restManagerClass));
+            }
+        }
+        return api;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RoomCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RoomCapability.java
new file mode 100644 (file)
index 0000000..cf1009c
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2010-2022 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 static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.commandToQuantity;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.action.RoomActions;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link RoomCapability} gives the ability to handle Room specifics
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class RoomCapability extends Capability {
+    private final Logger logger = LoggerFactory.getLogger(RoomCapability.class);
+    private Optional<EnergyCapability> energyCapability = Optional.empty();
+
+    public RoomCapability(CommonInterface handler) {
+        super(handler);
+    }
+
+    @Override
+    public void initialize() {
+        energyCapability = handler.getHomeCapability(EnergyCapability.class);
+    }
+
+    @Override
+    public void handleCommand(String channelName, Command command) {
+        if (CHANNEL_SETPOINT_MODE.equals(channelName)) {
+            try {
+                SetpointMode targetMode = SetpointMode.valueOf(command.toString());
+                if (targetMode == SetpointMode.MANUAL) {
+                    logger.info("Switch to 'Manual' mode is done by setting a setpoint temp, command ignored");
+                } else {
+                    energyCapability.ifPresent(cap -> cap.setRoomThermMode(handler.getId(), targetMode));
+                }
+            } catch (IllegalArgumentException e) {
+                logger.info("Command '{}' is not a valid setpoint mode for channel '{}'", command, channelName);
+            }
+        } else if (CHANNEL_VALUE.equals(channelName)) {
+            QuantityType<?> quantity = commandToQuantity(command, MeasureClass.INSIDE_TEMPERATURE);
+            if (quantity != null) {
+                energyCapability.ifPresent(cap -> cap.setRoomThermTemp(handler.getId(), quantity.doubleValue()));
+            } else {
+                logger.warn("Incorrect command '{}' on channel '{}'", command, channelName);
+            }
+        }
+    }
+
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return List.of(RoomActions.class);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java
new file mode 100644 (file)
index 0000000..80e0c0f
--- /dev/null
@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2010-2022 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.util.Collection;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.SecurityApi;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link SecurityCapability} is the base class for handler able to handle security features
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+class SecurityCapability extends RestCapability<SecurityApi> {
+    private final Logger logger = LoggerFactory.getLogger(SecurityCapability.class);
+
+    SecurityCapability(CommonInterface handler) {
+        super(handler, SecurityApi.class);
+    }
+
+    @Override
+    protected void updateHomeData(HomeData homeData) {
+        NAObjectMap<HomeDataPerson> persons = homeData.getPersons();
+        NAObjectMap<HomeDataModule> modules = homeData.getModules();
+        handler.getActiveChildren().forEach(childHandler -> {
+            String childId = childHandler.getId();
+            persons.getOpt(childId).ifPresentOrElse(person -> {
+                person.setIgnoredForThingUpdate(true);
+                childHandler.setNewData(person);
+            }, () -> {
+                modules.getOpt(childId).ifPresent(module -> {
+                    module.setIgnoredForThingUpdate(true);
+                    childHandler.setNewData(module);
+                });
+            });
+        });
+    }
+
+    @Override
+    protected void updateHomeStatus(HomeStatus homeStatus) {
+        NAObjectMap<HomeStatusPerson> persons = homeStatus.getPersons();
+        NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
+        handler.getActiveChildren().forEach(childHandler -> {
+            String childId = childHandler.getId();
+            persons.getOpt(childId).ifPresentOrElse(person -> childHandler.setNewData(person), () -> {
+                modules.getOpt(childId).ifPresentOrElse(module -> childHandler.setNewData(module), () -> {
+                    // This module is not present in the homestatus data, so it is considered as unreachable
+                    HomeStatusModule module = new HomeStatusModule();
+                    module.setReachable(false);
+                    childHandler.setNewData(module);
+                });
+            });
+        });
+    }
+
+    @Override
+    protected void updateHomeEvent(HomeEvent homeEvent) {
+        String personId = homeEvent.getPersonId();
+        if (personId != null) {
+            handler.getActiveChildren().stream().filter(handler -> personId.equals(handler.getId())).findFirst()
+                    .ifPresent(handler -> {
+                        homeEvent.setIgnoredForThingUpdate(true);
+                        handler.setNewData(homeEvent);
+                    });
+        }
+        String cameraId = homeEvent.getCameraId();
+        handler.getActiveChildren().stream().filter(handler -> cameraId.equals(handler.getId())).findFirst()
+                .ifPresent(handler -> {
+                    homeEvent.setIgnoredForThingUpdate(true);
+                    handler.setNewData(homeEvent);
+                });
+    }
+
+    public Collection<HomeEvent> getCameraEvents(String cameraId) {
+        return getApi().map(api -> {
+            try {
+                return api.getCameraEvents(handler.getId(), cameraId);
+            } catch (NetatmoException e) {
+                logger.warn("Error retrieving last events of camera '{}' : {}", cameraId, e.getMessage());
+                return null;
+            }
+        }).orElse(List.of());
+    }
+
+    public Collection<HomeEvent> getPersonEvents(String personId) {
+        return getApi().map(api -> {
+            try {
+                return api.getPersonEvents(handler.getId(), personId);
+            } catch (NetatmoException e) {
+                logger.warn("Error retrieving last events of person '{}' : {}", personId, e.getMessage());
+                return null;
+            }
+        }).orElse(List.of());
+    }
+
+    public void setPersonAway(String personId, boolean away) {
+        getApi().ifPresent(api -> {
+            try {
+                api.setPersonAwayStatus(handler.getId(), personId, away);
+                handler.expireData();
+            } catch (NetatmoException e) {
+                logger.warn("Error setting person away/at home '{}' : {}", personId, e.getMessage());
+            }
+        });
+    }
+
+    public @Nullable String ping(String vpnUrl) {
+        return getApi().map(api -> {
+            try {
+                return api.ping(vpnUrl);
+            } catch (NetatmoException e) {
+                logger.warn("Error pinging camera '{}' : {}", vpnUrl, e.getMessage());
+                return null;
+            }
+        }).orElse(null);
+    }
+
+    public void changeStatus(@Nullable String localURL, boolean status) {
+        if (localURL == null) {
+            logger.info("Monitoring changes can only be done on local camera.");
+            return;
+        }
+        getApi().ifPresent(api -> {
+            try {
+                api.changeStatus(localURL, status);
+                handler.expireData();
+            } catch (NetatmoException e) {
+                logger.warn("Error changing camera monitoring status '{}' : {}", status, e.getMessage());
+            }
+        });
+    }
+
+    public void changeFloodlightMode(@Nullable String localURL, FloodLightMode mode) {
+        if (localURL == null) {
+            logger.info("Changing floodlight mode can only be done on local camera.");
+            return;
+        }
+        getApi().ifPresent(api -> {
+            try {
+                api.changeFloodLightMode(localURL, mode);
+                handler.expireData();
+            } catch (NetatmoException e) {
+                logger.warn("Error changing Presence floodlight mode '{}' : {}", mode, e.getMessage());
+            }
+        });
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/WeatherCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/WeatherCapability.java
new file mode 100644 (file)
index 0000000..b5ddb31
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2022 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.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.WeatherApi;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link WeatherCapability} give the ability to read weather station api
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WeatherCapability extends RestCapability<WeatherApi> {
+    private final Logger logger = LoggerFactory.getLogger(WeatherCapability.class);
+
+    public WeatherCapability(CommonInterface handler) {
+        super(handler, WeatherApi.class);
+    }
+
+    @Override
+    protected List<NAObject> updateReadings(WeatherApi api) {
+        try {
+            return List.of(api.getStationData(handler.getId()));
+        } catch (NetatmoException e) {
+            logger.warn("Error retrieving weather data '{}' : {}", handler.getId(), e.getMessage());
+        }
+        return List.of();
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityChannelHelper.java
new file mode 100644 (file)
index 0000000..7895d76
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link AirQualityChannelHelper} handles specific channels of things handling ppm measurement
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class AirQualityChannelHelper extends ChannelHelper {
+
+    public AirQualityChannelHelper() {
+        this(GROUP_AIR_QUALITY);
+    }
+
+    protected AirQualityChannelHelper(String groupName) {
+        super(groupName, MeasureClass.CO2);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        return CHANNEL_CO2.equals(channelId) ? toQuantityType(dashboard.getCo2(), MeasureClass.CO2) : null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityExtChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityExtChannelHelper.java
new file mode 100644 (file)
index 0000000..7cf618f
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link AirQualityExtChannelHelper} handles specific channels of NHC thing.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class AirQualityExtChannelHelper extends AirQualityChannelHelper {
+
+    public AirQualityExtChannelHelper() {
+        super(GROUP_TYPE_AIR_QUALITY_EXTENDED);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        return CHANNEL_HEALTH_INDEX.equals(channelId) ? new DecimalType(dashboard.getHealthIdx())
+                : super.internalGetDashboard(channelId, dashboard);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryChannelHelper.java
new file mode 100644 (file)
index 0000000..beaf93e
--- /dev/null
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.Module;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link BatteryChannelHelper} handles specific channels of modules using batteries
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class BatteryChannelHelper extends ChannelHelper {
+
+    public BatteryChannelHelper() {
+        super(GROUP_BATTERY);
+    }
+
+    protected BatteryChannelHelper(String groupName) {
+        super(groupName);
+    }
+
+    @Override
+    protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+        int percent = -1;
+        if (naThing instanceof Module) {
+            percent = ((Module) naThing).getBatteryPercent();
+        }
+        if (naThing instanceof HomeStatusModule) {
+            percent = ((HomeStatusModule) naThing).getBatteryState().level;
+        }
+        switch (channelId) {
+            case CHANNEL_VALUE:
+                if (percent >= 0) {
+                    return new DecimalType(percent);
+                }
+            case CHANNEL_LOW_BATTERY:
+                if (percent >= 0) {
+                    return OnOffType.from(percent < 20);
+                }
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryExtChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryExtChannelHelper.java
new file mode 100644 (file)
index 0000000..645601b
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.Module;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link BatteryExtChannelHelper} handles specific channels of modules using batteries
+ * having battery status information available on top of standard information
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class BatteryExtChannelHelper extends BatteryChannelHelper {
+
+    public BatteryExtChannelHelper() {
+        super(GROUP_TYPE_BATTERY_EXTENDED);
+    }
+
+    @Override
+    protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+        if (CHANNEL_BATTERY_STATUS.equals(channelId)) {
+            if (naThing instanceof Module) {
+                return toStringType(((Module) naThing).getBatteryState());
+            }
+            if (naThing instanceof HomeStatusModule) {
+                return toStringType(((HomeStatusModule) naThing).getBatteryState());
+            }
+        }
+        return super.internalGetProperty(channelId, naThing, config);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java
new file mode 100644 (file)
index 0000000..db73b2d
--- /dev/null
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link CameraChannelHelper} handles specific channels of cameras
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class CameraChannelHelper extends ChannelHelper {
+    private static final String QUALITY_CONF_ENTRY = "quality";
+    private static final String LIVE_PICTURE = "/live/snapshot_720.jpg";
+    private boolean isLocal;
+    private @Nullable String vpnUrl;
+    private @Nullable String localUrl;
+
+    public CameraChannelHelper() {
+        super(GROUP_CAM_STATUS, GROUP_CAM_LIVE);
+    }
+
+    public void setUrls(String vpnUrl, @Nullable String localUrl) {
+        this.localUrl = localUrl;
+        this.vpnUrl = vpnUrl;
+        this.isLocal = localUrl != null;
+    }
+
+    public @Nullable String getLocalURL() {
+        return localUrl;
+    }
+
+    @Override
+    protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+        if (naThing instanceof HomeStatusModule) {
+            HomeStatusModule camera = (HomeStatusModule) naThing;
+            boolean isMonitoring = OnOffType.ON.equals(camera.getMonitoring());
+            switch (channelId) {
+                case CHANNEL_MONITORING:
+                    return camera.getMonitoring();
+                case CHANNEL_SD_CARD:
+                    return toStringType(camera.getSdStatus());
+                case CHANNEL_ALIM_STATUS:
+                    return toStringType(camera.getAlimStatus());
+                case CHANNEL_LIVEPICTURE_VPN_URL:
+                    return toStringType(getLivePictureURL(false, isMonitoring));
+                case CHANNEL_LIVEPICTURE_LOCAL_URL:
+                    return toStringType(getLivePictureURL(true, isMonitoring));
+                case CHANNEL_LIVEPICTURE:
+                    return toRawType(getLivePictureURL(isLocal, isMonitoring));
+                case CHANNEL_LIVESTREAM_VPN_URL:
+                    return getLiveStreamURL(false, (String) config.get(QUALITY_CONF_ENTRY), isMonitoring);
+                case CHANNEL_LIVESTREAM_LOCAL_URL:
+                    return getLiveStreamURL(true, (String) config.get(QUALITY_CONF_ENTRY), isMonitoring);
+            }
+        }
+        return null;
+    }
+
+    private @Nullable String getLivePictureURL(boolean local, boolean isMonitoring) {
+        String url = local ? localUrl : vpnUrl;
+        if (!isMonitoring || (local && !isLocal) || url == null) {
+            return null;
+        }
+        return String.format("%s%s", url, LIVE_PICTURE);
+    }
+
+    private State getLiveStreamURL(boolean local, @Nullable String configQual, boolean isMonitoring) {
+        String url = local ? localUrl : vpnUrl;
+        if (!isMonitoring || (local && !isLocal) || url == null) {
+            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");
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/ChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/ChannelHelper.java
new file mode 100644 (file)
index 0000000..a5c9adb
--- /dev/null
@@ -0,0 +1,118 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.binding.netatmo.internal.api.dto.Event;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.binding.netatmo.internal.providers.NetatmoThingTypeProvider;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link ChannelHelper} is the base class for all channel helpers.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public abstract class ChannelHelper {
+    private @Nullable NAObject data;
+    private final Set<String> channelGroupTypes;
+    private final Set<String> channelGroups = new HashSet<>();
+    private Set<String> extensibleChannels = Set.of();
+
+    ChannelHelper(String... providedGroups) {
+        this.channelGroupTypes = Set.of(providedGroups);
+        channelGroupTypes.forEach(groupType -> channelGroups.add(NetatmoThingTypeProvider.toGroupName(groupType)));
+    }
+
+    ChannelHelper(String providedGroup, MeasureClass measureClass) {
+        this(providedGroup);
+        this.extensibleChannels = measureClass.channels.keySet();
+    }
+
+    public void setNewData(@Nullable NAObject data) {
+        this.data = data;
+    }
+
+    public final @Nullable State getChannelState(String channelId, @Nullable String groupId, Configuration config) {
+        State result = null;
+        if (channelGroups.isEmpty() || (groupId != null && channelGroups.contains(groupId))) {
+            NAObject localData = data;
+            if (localData instanceof Event) {
+                result = internalGetEvent(channelId, (Event) localData);
+                if (result != null) {
+                    return result;
+                }
+            }
+            if (localData instanceof NAThing) {
+                NAThing naThing = (NAThing) localData;
+                result = internalGetProperty(channelId, naThing, config);
+                if (result != null) {
+                    return result;
+                }
+                Dashboard dashboard = naThing.getDashboardData();
+                if (dashboard != null) {
+                    result = internalGetDashboard(channelId, dashboard);
+                    if (result != null) {
+                        return result;
+                    }
+                }
+            }
+            if (localData instanceof NAObject) {
+                result = internalGetObject(channelId, localData);
+                if (result != null) {
+                    return result;
+                }
+            }
+            result = internalGetOther(channelId);
+        }
+        return result;
+    }
+
+    protected @Nullable State internalGetObject(String channelId, NAObject localData) {
+        return null;
+    }
+
+    protected @Nullable State internalGetOther(String channelId) {
+        return null;
+    }
+
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        return null;
+    }
+
+    protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+        return null;
+    }
+
+    protected @Nullable State internalGetEvent(String channelId, Event event) {
+        return null;
+    }
+
+    public Set<String> getChannelGroupTypes() {
+        return channelGroupTypes;
+    }
+
+    public Set<String> getExtensibleChannels() {
+        return extensibleChannels;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventChannelHelper.java
new file mode 100644 (file)
index 0000000..2ac950b
--- /dev/null
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
+
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.dto.Event;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link EventChannelHelper} handles specific channels of cameras
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class EventChannelHelper extends ChannelHelper {
+    private boolean isLocal;
+    private @Nullable ZonedDateTime lastEventTime;
+    private @Nullable String vpnUrl, localUrl;
+    private ModuleType moduleType = ModuleType.UNKNOWN;
+
+    public EventChannelHelper() {
+        this(GROUP_LAST_EVENT);
+    }
+
+    protected EventChannelHelper(String groupName) {
+        super(groupName);
+    }
+
+    public void setModuleType(ModuleType moduleType) {
+        this.moduleType = moduleType;
+    }
+
+    public void setUrls(String vpnUrl, @Nullable String localUrl) {
+        this.localUrl = localUrl;
+        this.vpnUrl = vpnUrl;
+        this.isLocal = localUrl != null;
+    }
+
+    @Override
+    public void setNewData(@Nullable NAObject data) {
+        if (data instanceof Event) {
+            Event event = (Event) data;
+            ZonedDateTime localLast = lastEventTime;
+            ZonedDateTime eventTime = event.getTime();
+            if ((localLast != null && !eventTime.isAfter(localLast)) || !event.getEventType().appliesOn(moduleType)) {
+                return; // ignore incoming events if they are deprecated
+            }
+            lastEventTime = eventTime;
+        }
+        super.setNewData(data);
+    }
+
+    @Override
+    protected @Nullable State internalGetEvent(String channelId, Event event) {
+        switch (channelId) {
+            case CHANNEL_EVENT_TYPE:
+                return toStringType(event.getEventType());
+            case CHANNEL_EVENT_MESSAGE:
+                return toStringType(event.getName());
+            case CHANNEL_EVENT_TIME:
+                return new DateTimeType(event.getTime());
+            case CHANNEL_EVENT_PERSON_ID:
+                return toStringType(event.getPersonId());
+            case CHANNEL_EVENT_CAMERA_ID:
+                return toStringType(event.getCameraId());
+            case CHANNEL_EVENT_SUBTYPE:
+                return event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL);
+            case CHANNEL_EVENT_SNAPSHOT:
+                return toRawType(event.getSnapshotUrl());
+            case CHANNEL_EVENT_SNAPSHOT_URL:
+                return toStringType(event.getSnapshotUrl());
+        }
+        if (event instanceof HomeEvent) {
+            HomeEvent homeEvent = (HomeEvent) event;
+            switch (channelId) {
+                case CHANNEL_EVENT_VIDEO_STATUS:
+                    return homeEvent.getVideoId() != null ? toStringType(homeEvent.getVideoStatus()) : UnDefType.NULL;
+                case CHANNEL_EVENT_VIDEO_LOCAL_URL:
+                    return getStreamURL(true, homeEvent.getVideoId());
+                case CHANNEL_EVENT_VIDEO_VPN_URL:
+                    return getStreamURL(false, homeEvent.getVideoId());
+            }
+        }
+        return null;
+    }
+
+    private State getStreamURL(boolean local, @Nullable String videoId) {
+        String url = local ? localUrl : vpnUrl;
+        if ((local && !isLocal) || url == null || videoId == null) {
+            return UnDefType.NULL;
+        }
+        return toStringType("%s/vod/%s/index.m3u8", url, videoId);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventPersonChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventPersonChannelHelper.java
new file mode 100644 (file)
index 0000000..ff4054b
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.dto.Event;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link EventPersonChannelHelper} handles specific channels of person events
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class EventPersonChannelHelper extends EventChannelHelper {
+    public EventPersonChannelHelper() {
+        super(GROUP_PERSON_EVENT);
+    }
+
+    @Override
+    protected @Nullable State internalGetEvent(String channelId, Event event) {
+        EventType eventType = event.getEventType();
+        if (eventType.appliesOn(ModuleType.PERSON) && CHANNEL_PERSON_AT_HOME.equals(channelId)) {
+            return OnOffType.from(EventType.PERSON.equals(eventType) || EventType.PERSON_HOME.equals(eventType));
+        }
+        return super.internalGetEvent(channelId, event);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeEnergyChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeEnergyChannelHelper.java
new file mode 100644 (file)
index 0000000..f594c1e
--- /dev/null
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
+
+import java.time.DayOfWeek;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.binding.netatmo.internal.api.dto.ThermProgram;
+import org.openhab.binding.netatmo.internal.api.dto.TimeTableItem;
+import org.openhab.binding.netatmo.internal.api.dto.Zone;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link HomeEnergyChannelHelper} handles specific channels of thermostat settings at home level.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HomeEnergyChannelHelper extends ChannelHelper {
+
+    public HomeEnergyChannelHelper() {
+        super(GROUP_ENERGY);
+    }
+
+    @Override
+    protected @Nullable State internalGetProperty(String channelId, NAThing data, Configuration config) {
+        if (data instanceof HomeData) {
+            HomeData homeData = (HomeData) data;
+            SetpointMode thermMode = homeData.getThermMode();
+            ThermProgram currentProgram = homeData.getActiveProgram();
+            switch (channelId) {
+                case CHANNEL_SETPOINT_DURATION:
+                    return toQuantityType(homeData.getThermSetpointDefaultDuration(), Units.MINUTE);
+                case CHANNEL_PLANNING:
+                    return (currentProgram != null ? toStringType(currentProgram.getName()) : null);
+                case CHANNEL_SETPOINT_END_TIME:
+                    switch (thermMode) {
+                        case PROGRAM:
+                        case HOME:
+                        case SCHEDULE:
+                            return currentProgram != null ? toDateTimeType(nextProgramTime(currentProgram))
+                                    : UnDefType.UNDEF;
+                        default:
+                            return UnDefType.UNDEF;
+                    }
+                case CHANNEL_SETPOINT_MODE:
+                    switch (thermMode) {
+                        case OFF:
+                        case MAX:
+                        case UNKNOWN:
+                            return UnDefType.UNDEF;
+                        case PROGRAM:
+                        case HOME:
+                        case SCHEDULE:
+                            if (currentProgram != null) {
+                                TimeTableItem currentProgramMode = currentProgramMode(currentProgram);
+                                if (currentProgramMode != null) {
+                                    Zone zone = currentProgram.getZone(String.valueOf(currentProgramMode.getZoneId()));
+                                    if (zone != null) {
+                                        return new StringType(zone.getName());
+                                    }
+                                }
+                            }
+                            return UnDefType.NULL;
+                        default:
+                            return toStringType(thermMode);
+                    }
+            }
+        }
+        return null;
+    }
+
+    private static ZonedDateTime programBaseTimeZdt() {
+        return ZonedDateTime.now().with(DayOfWeek.MONDAY).truncatedTo(ChronoUnit.DAYS);
+    }
+
+    private static long minutesSinceProgramBaseTime() {
+        return ChronoUnit.MINUTES.between(programBaseTimeZdt(), ZonedDateTime.now());
+    }
+
+    private static @Nullable TimeTableItem currentProgramMode(ThermProgram activeProgram) {
+        long diff = minutesSinceProgramBaseTime();
+        return activeProgram.getTimetable().stream().filter(t -> t.getMinuteOffset() < diff)
+                .reduce((first, second) -> second).orElse(null);
+    }
+
+    private static ZonedDateTime nextProgramTime(ThermProgram activeProgram) {
+        long diff = minutesSinceProgramBaseTime();
+        // By default we'll use the first slot of next week - this case will be true if
+        // we are in the last schedule of the week so below loop will not exit by break
+        List<TimeTableItem> timetable = activeProgram.getTimetable();
+        int next = timetable.get(0).getMinuteOffset() + (7 * 24 * 60);
+        for (TimeTableItem timeTable : timetable) {
+            if (timeTable.getMinuteOffset() > diff) {
+                next = timeTable.getMinuteOffset();
+                break;
+            }
+        }
+        return programBaseTimeZdt().plusMinutes(next);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeSecurityChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeSecurityChannelHelper.java
new file mode 100644 (file)
index 0000000..81140ce
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toRawType;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link HomeSecurityChannelHelper} handles specific information for security purpose.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HomeSecurityChannelHelper extends ChannelHelper {
+    private long persons = -1;
+    private long unknowns = -1;
+    private @Nullable String unknownSnapshot;
+    private List<String> knownIds = List.of();
+
+    public HomeSecurityChannelHelper() {
+        super(GROUP_SECURITY);
+    }
+
+    @Override
+    public void setNewData(@Nullable NAObject data) {
+        super.setNewData(data);
+        if (data instanceof HomeData) {
+            HomeData homeData = (HomeData) data;
+            knownIds = homeData.getPersons().values().stream().filter(person -> person.isKnown()).map(p -> p.getId())
+                    .collect(Collectors.toList());
+        }
+        if (data instanceof HomeStatus) {
+            HomeStatus status = (HomeStatus) data;
+            NAObjectMap<HomeStatusPerson> allPersons = status.getPersons();
+            List<HomeStatusPerson> present = allPersons.values().stream().filter(p -> !p.isOutOfSight())
+                    .collect(Collectors.toList());
+
+            persons = present.size();
+            unknowns = present.stream().filter(person -> !knownIds.contains(person.getId())).count();
+        }
+    }
+
+    @Override
+    protected @Nullable State internalGetOther(String channelId) {
+        switch (channelId) {
+            case CHANNEL_PERSON_COUNT:
+                return persons != -1 ? new DecimalType(persons) : UnDefType.NULL;
+            case CHANNEL_UNKNOWN_PERSON_COUNT:
+                return unknowns != -1 ? new DecimalType(unknowns) : UnDefType.NULL;
+            case CHANNEL_UNKNOWN_PERSON_PICTURE:
+                return unknownSnapshot != null ? toRawType(unknownSnapshot) : UnDefType.NULL;
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HumidityChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HumidityChannelHelper.java
new file mode 100644 (file)
index 0000000..09ba0f7
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType;
+import static org.openhab.binding.netatmo.internal.utils.WeatherUtils.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link HumidityChannelHelper} handles specific channels of modules returning humidity measures.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HumidityChannelHelper extends ChannelHelper {
+
+    public HumidityChannelHelper() {
+        super(GROUP_HUMIDITY, MeasureClass.HUMIDITY);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        switch (channelId) {
+            case CHANNEL_HUMIDEX:
+                return new DecimalType(humidex(dashboard.getTemperature(), dashboard.getHumidity()));
+            case CHANNEL_HUMIDEX_SCALE:
+                return new DecimalType(humidexScale(humidex(dashboard.getTemperature(), dashboard.getHumidity())));
+            case CHANNEL_VALUE:
+                return toQuantityType(dashboard.getHumidity(), MeasureClass.HUMIDITY);
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/LocationChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/LocationChannelHelper.java
new file mode 100644 (file)
index 0000000..1ece7b4
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.Device;
+import org.openhab.binding.netatmo.internal.api.dto.Home;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link LocationChannelHelper} handles specific channels of modules holding a location data
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class LocationChannelHelper extends ChannelHelper {
+
+    public LocationChannelHelper() {
+        super(GROUP_LOCATION);
+    }
+
+    @Override
+    protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+        if (CHANNEL_VALUE.equals(channelId)) {
+            State point = UnDefType.UNDEF;
+            if (naThing instanceof Home) {
+                point = ((Home) naThing).getLocation();
+            } else if (naThing instanceof Device) {
+                point = ((Device) naThing).getPlace().map(place -> place.getLocation()).orElse(point);
+            }
+            return point;
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/MeasuresChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/MeasuresChannelHelper.java
new file mode 100644 (file)
index 0000000..f04ca98
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link MeasuresChannelHelper} handles extensible channels based on getMeasure endpoint.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class MeasuresChannelHelper extends ChannelHelper {
+    private @Nullable Map<String, State> measures;
+
+    public void setMeasures(Map<String, State> measures) {
+        this.measures = measures;
+    }
+
+    @Override
+    protected @Nullable State internalGetOther(String channelId) {
+        Map<String, State> localMeasures = measures;
+        if (localMeasures != null) {
+            return localMeasures.get(channelId);
+        }
+        throw new IllegalArgumentException("localMeasures should not be null, please file a bug report.");
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/NoiseChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/NoiseChannelHelper.java
new file mode 100644 (file)
index 0000000..d0d4a60
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link NoiseChannelHelper} handles specific channels of modules measuring sound level
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class NoiseChannelHelper extends ChannelHelper {
+
+    public NoiseChannelHelper() {
+        super(GROUP_NOISE, MeasureClass.NOISE);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        return CHANNEL_VALUE.equals(channelId) ? toQuantityType(dashboard.getNoise(), MeasureClass.NOISE) : null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PersonChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PersonChannelHelper.java
new file mode 100644 (file)
index 0000000..c730f29
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link PersonChannelHelper} handles channels of person things.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class PersonChannelHelper extends ChannelHelper {
+
+    public PersonChannelHelper() {
+        super(GROUP_PERSON);
+    }
+
+    @Override
+    protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+        if (naThing instanceof HomeDataPerson) {
+            HomeDataPerson person = (HomeDataPerson) naThing;
+            switch (channelId) {
+                case CHANNEL_PERSON_AVATAR_URL:
+                    return toStringType(person.getUrl().orElse(null));
+                case CHANNEL_PERSON_AVATAR:
+                    return toRawType(person.getUrl().orElse(null));
+            }
+        }
+        if (naThing instanceof HomeStatusPerson) {
+            HomeStatusPerson person = (HomeStatusPerson) naThing;
+            switch (channelId) {
+                case CHANNEL_PERSON_AT_HOME:
+                    return OnOffType.from(!person.isOutOfSight());
+                case CHANNEL_LAST_SEEN:
+                    return toDateTimeType(person.getLastSeen());
+            }
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PresenceChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PresenceChannelHelper.java
new file mode 100644 (file)
index 0000000..c2449c9
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link PresenceChannelHelper} handles specific channels of Presence external cameras
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class PresenceChannelHelper extends ChannelHelper {
+    public PresenceChannelHelper() {
+        super(GROUP_PRESENCE);
+    }
+
+    @Override
+    protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+        if (naThing instanceof HomeStatusModule) {
+            HomeStatusModule camera = (HomeStatusModule) naThing;
+            switch (channelId) {
+                case CHANNEL_FLOODLIGHT:
+                    return toStringType(camera.getFloodlight());
+            }
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureChannelHelper.java
new file mode 100644 (file)
index 0000000..a2432f0
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link PressureChannelHelper} handles specific channels of modules measuring pressure
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class PressureChannelHelper extends ChannelHelper {
+
+    public PressureChannelHelper() {
+        this(GROUP_PRESSURE);
+    }
+
+    protected PressureChannelHelper(String groupName) {
+        super(groupName, MeasureClass.PRESSURE);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        switch (channelId) {
+            case CHANNEL_VALUE:
+                return toQuantityType(dashboard.getPressure(), MeasureClass.PRESSURE);
+            case CHANNEL_ABSOLUTE_PRESSURE:
+                return toQuantityType(dashboard.getAbsolutePressure(), MeasureClass.PRESSURE);
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureExtChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureExtChannelHelper.java
new file mode 100644 (file)
index 0000000..3df7bd9
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link PressureExtChannelHelper} handles specific behavior of modules measuring pressure
+ * with pressure trend capability
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class PressureExtChannelHelper extends PressureChannelHelper {
+
+    public PressureExtChannelHelper() {
+        super(GROUP_TYPE_PRESSURE_EXTENDED);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        return channelId.equals(CHANNEL_TREND) ? toStringType(dashboard.getPressureTrend())
+                : super.internalGetDashboard(channelId, dashboard);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RainChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RainChannelHelper.java
new file mode 100644 (file)
index 0000000..08521bf
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link RainChannelHelper} handles specific channels of modules measuring rain
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class RainChannelHelper extends ChannelHelper {
+
+    public RainChannelHelper() {
+        super(GROUP_RAIN, MeasureClass.RAIN_QUANTITY);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        switch (channelId) {
+            case CHANNEL_VALUE:
+                return toQuantityType(dashboard.getRain(), MeasureClass.RAIN_INTENSITY);
+            case CHANNEL_SUM_RAIN1:
+                return toQuantityType(dashboard.getSumRain1(), MeasureClass.RAIN_QUANTITY);
+            case CHANNEL_SUM_RAIN24:
+                return toQuantityType(dashboard.getSumRain24(), MeasureClass.RAIN_QUANTITY);
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RoomChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RoomChannelHelper.java
new file mode 100644 (file)
index 0000000..2914d2e
--- /dev/null
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.api.dto.Room;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link RoomChannelHelper} handles specific channels of the room
+ *
+ * @author Markus Dillmann - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class RoomChannelHelper extends ChannelHelper {
+
+    public RoomChannelHelper() {
+        super(GROUP_ROOM_PROPERTIES, GROUP_ROOM_TEMPERATURE);
+    }
+
+    @Override
+    protected @Nullable State internalGetObject(String channelId, NAObject naObject) {
+        if (naObject instanceof Room) {
+            Room room = (Room) naObject;
+            switch (channelId) {
+                case CHANNEL_ROOM_WINDOW_OPEN:
+                    return room.hasOpenedWindows();
+                case CHANNEL_ANTICIPATING:
+                    return room.isAnticipating();
+                case CHANNEL_ROOM_HEATING_POWER:
+                    return toQuantityType(room.getHeatingPowerRequest(), Units.PERCENT);
+                case CHANNEL_VALUE:
+                    return toQuantityType(room.getMeasuredTemp(), MeasureClass.INSIDE_TEMPERATURE);
+            }
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SetpointChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SetpointChannelHelper.java
new file mode 100644 (file)
index 0000000..56d9f87
--- /dev/null
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.api.dto.Room;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link SetpointChannelHelper} handles channels for a room capable of managing a thermostat
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class SetpointChannelHelper extends ChannelHelper {
+
+    public SetpointChannelHelper() {
+        super(GROUP_TH_SETPOINT);
+    }
+
+    @Override
+    protected @Nullable State internalGetObject(String channelId, NAObject naObject) {
+        if (naObject instanceof Room) {
+            Room room = (Room) naObject;
+            switch (channelId) {
+                case CHANNEL_SETPOINT_MODE:
+                    return toStringType(room.getSetpointMode().name());
+                case CHANNEL_SETPOINT_START_TIME:
+                    return toDateTimeType(room.getSetpointBegin());
+                case CHANNEL_SETPOINT_END_TIME:
+                    return toDateTimeType(room.getSetpointEnd());
+                case CHANNEL_VALUE:
+                    switch (room.getSetpointMode()) {
+                        case OFF:
+                        case MAX:
+                            return UnDefType.UNDEF;
+                        case AWAY:
+                        case HOME:
+                        case MANUAL:
+                        case SCHEDULE:
+                        case FROST_GUARD:
+                        case PROGRAM:
+                            return toQuantityType(room.getSetpointTemp(), MeasureClass.INSIDE_TEMPERATURE);
+                        case UNKNOWN:
+                            return UnDefType.NULL;
+                    }
+            }
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SignalChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SignalChannelHelper.java
new file mode 100644 (file)
index 0000000..13715ac
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link SignalChannelHelper} handles specific behavior of WIFI or RF devices and modules
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class SignalChannelHelper extends ChannelHelper {
+
+    public SignalChannelHelper() {
+        super(GROUP_SIGNAL);
+    }
+
+    @Override
+    protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+        int status = naThing.getRadioStatus();
+        if (status != -1) {
+            switch (channelId) {
+                case CHANNEL_SIGNAL_STRENGTH:
+                    return new DecimalType(getSignalStrength(status, naThing.getType().getSignalLevels()));
+                case CHANNEL_VALUE:
+                    return toQuantityType(status, Units.DECIBEL_MILLIWATTS);
+            }
+        }
+        return null;
+    }
+
+    private int getSignalStrength(int signalLevel, int[] levels) {
+        int level;
+        for (level = 0; level < levels.length; level++) {
+            if (signalLevel > levels[level]) {
+                break;
+            }
+        }
+        return level;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureChannelHelper.java
new file mode 100644 (file)
index 0000000..bef052b
--- /dev/null
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
+import static org.openhab.binding.netatmo.internal.utils.WeatherUtils.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link TemperatureChannelHelper} handles channels of modules measuring temperature
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TemperatureChannelHelper extends ChannelHelper {
+    /*
+     * TemperatureChannelHelper may be used by indoor or outdoor modules. There is no easy way here to decide what is
+     * the handler owning the channelHelper. The usage of OUTSIDE_TEMPERATURE instead of INSIDE_TEMPERATURE is by design
+     * because OUTSIDE_TEMPERATURE has wide value range than INSIDE_TEMPERATURE.
+     */
+    public TemperatureChannelHelper() {
+        this(GROUP_TEMPERATURE, MeasureClass.OUTSIDE_TEMPERATURE);
+    }
+
+    protected TemperatureChannelHelper(String groupName, MeasureClass measureClass) {
+        super(groupName, measureClass);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        switch (channelId) {
+            case CHANNEL_VALUE:
+                return toQuantityType(dashboard.getTemperature(), MeasureClass.OUTSIDE_TEMPERATURE);
+            case CHANNEL_MIN_VALUE:
+                return toQuantityType(dashboard.getMinTemp(), MeasureClass.OUTSIDE_TEMPERATURE);
+            case CHANNEL_MAX_VALUE:
+                return toQuantityType(dashboard.getMaxTemp(), MeasureClass.OUTSIDE_TEMPERATURE);
+            case CHANNEL_MIN_TIME:
+                return toDateTimeType(dashboard.getDateMinTemp());
+            case CHANNEL_MAX_TIME:
+                return toDateTimeType(dashboard.getDateMaxTemp());
+            case CHANNEL_HEAT_INDEX:
+                return toQuantityType(heatIndex(dashboard.getTemperature(), dashboard.getHumidity()),
+                        MeasureClass.HEAT_INDEX);
+            case CHANNEL_DEWPOINT:
+                return toQuantityType(dewPoint(dashboard.getTemperature(), dashboard.getHumidity()),
+                        MeasureClass.OUTSIDE_TEMPERATURE);
+            case CHANNEL_DEWPOINT_DEP:
+                double dewPoint = dewPoint(dashboard.getTemperature(), dashboard.getHumidity());
+                return toQuantityType(dewPointDep(dashboard.getTemperature(), dewPoint),
+                        MeasureClass.OUTSIDE_TEMPERATURE);
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureExtChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureExtChannelHelper.java
new file mode 100644 (file)
index 0000000..b97a154
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link TemperatureExtChannelHelper} handles specific channels of modules measuring temperature
+ * with temp trend capability
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TemperatureExtChannelHelper extends TemperatureChannelHelper {
+    public TemperatureExtChannelHelper() {
+        super(GROUP_TYPE_TEMPERATURE_EXTENDED, MeasureClass.INSIDE_TEMPERATURE);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        return CHANNEL_TREND.equals(channelId) ? toStringType(dashboard.getTempTrend())
+                : super.internalGetDashboard(channelId, dashboard);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureOutChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureOutChannelHelper.java
new file mode 100644 (file)
index 0000000..af00c75
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link TemperatureOutChannelHelper} handles specific channels of modules measuring temperature
+ * with temp trend capability
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TemperatureOutChannelHelper extends TemperatureChannelHelper {
+    public TemperatureOutChannelHelper() {
+        super(GROUP_TYPE_TEMPERATURE_OUTSIDE, MeasureClass.OUTSIDE_TEMPERATURE);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        return CHANNEL_TREND.equals(channelId) ? toStringType(dashboard.getTempTrend())
+                : super.internalGetDashboard(channelId, dashboard);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/Therm1ChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/Therm1ChannelHelper.java
new file mode 100644 (file)
index 0000000..5cb2a2b
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link Therm1ChannelHelper} handles specific behavior of the thermostat module
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class Therm1ChannelHelper extends ChannelHelper {
+
+    public Therm1ChannelHelper() {
+        super(GROUP_TH_PROPERTIES);
+    }
+
+    @Override
+    protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+        return (naThing instanceof HomeStatusModule && CHANNEL_THERM_RELAY.equals(channelId))
+                ? ((HomeStatusModule) naThing).getBoilerStatus()
+                : null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampChannelHelper.java
new file mode 100644 (file)
index 0000000..8c8dbba
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toDateTimeType;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link TimestampChannelHelper} handles specific behavior
+ * of modules reporting last seen
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TimestampChannelHelper extends ChannelHelper {
+
+    public TimestampChannelHelper() {
+        this(GROUP_TIMESTAMP);
+    }
+
+    protected TimestampChannelHelper(String groupName) {
+        super(groupName);
+    }
+
+    @Override
+    protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+        Optional<ZonedDateTime> lastSeen = naThing.getLastSeen();
+        return CHANNEL_LAST_SEEN.equals(channelId) && lastSeen.isPresent() ? toDateTimeType(lastSeen) : null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampExtChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampExtChannelHelper.java
new file mode 100644 (file)
index 0000000..e3e614e
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toDateTimeType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link TimestampExtChannelHelper} handles specific behavior
+ * of modules reporting measurement timestamp in dashboard
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TimestampExtChannelHelper extends TimestampChannelHelper {
+
+    public TimestampExtChannelHelper() {
+        super(GROUP_TYPE_TIMESTAMP_EXTENDED);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        return CHANNEL_MEASURES_TIMESTAMP.equals(channelId) ? toDateTimeType(dashboard.getTimeUtc()) : null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/WindChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/WindChannelHelper.java
new file mode 100644 (file)
index 0000000..8ab2b7b
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2022 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.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link WindChannelHelper} handle specifics channels of modules measuring wind
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WindChannelHelper extends ChannelHelper {
+
+    public WindChannelHelper() {
+        super(GROUP_WIND);
+    }
+
+    @Override
+    protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+        switch (channelId) {
+            case CHANNEL_WIND_ANGLE:
+                return toQuantityType(dashboard.getWindAngle(), MeasureClass.WIND_ANGLE);
+            case CHANNEL_WIND_STRENGTH:
+                return toQuantityType(dashboard.getWindStrength(), MeasureClass.WIND_SPEED);
+            case CHANNEL_GUST_ANGLE:
+                return toQuantityType(dashboard.getGustAngle(), MeasureClass.WIND_ANGLE);
+            case CHANNEL_GUST_STRENGTH:
+                return toQuantityType(dashboard.getGustStrength(), MeasureClass.WIND_SPEED);
+            case CHANNEL_MAX_WIND_STRENGTH:
+                return toQuantityType(dashboard.getMaxWindStr(), MeasureClass.WIND_SPEED);
+            case CHANNEL_DATE_MAX_WIND_STRENGTH:
+                return toDateTimeType(dashboard.getDateMaxWindStr());
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/homecoach/NAHealthyHomeCoachHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/homecoach/NAHealthyHomeCoachHandler.java
deleted file mode 100644 (file)
index 9c7c543..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.homecoach;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAHealthyHomeCoach;
-
-/**
- * {@link NAHealthyHomeCoachHandler} is the class used to handle the Health Home Coach device
- *
- * @author Michael Svinth - Initial contribution OH2 version
- *
- */
-@NonNullByDefault
-public class NAHealthyHomeCoachHandler extends NetatmoDeviceHandler<NAHealthyHomeCoach> {
-
-    public NAHealthyHomeCoachHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected Optional<NAHealthyHomeCoach> updateReadings() {
-        return getBridgeHandler().flatMap(handler -> handler.getHomecoachDataBody(getId()))
-                .map(dataBody -> nonNullStream(dataBody.getDevices())
-                        .filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
-    }
-
-    @Override
-    protected void updateProperties(NAHealthyHomeCoach deviceData) {
-        updateProperties(deviceData.getFirmware(), deviceData.getType());
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        NADashboardData dashboardData = getDevice().map(d -> d.getDashboardData()).orElse(null);
-        if (dashboardData != null) {
-            switch (channelId) {
-                case CHANNEL_CO2:
-                    return toQuantityType(dashboardData.getCo2(), API_CO2_UNIT);
-                case CHANNEL_TEMPERATURE:
-                    return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
-                case CHANNEL_HEALTH_INDEX:
-                    return toStringType(toHealthIndexString(dashboardData.getHealthIdx()));
-                case CHANNEL_MIN_TEMP:
-                    return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
-                case CHANNEL_MAX_TEMP:
-                    return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
-                case CHANNEL_TEMP_TREND:
-                    return toStringType(dashboardData.getTempTrend());
-                case CHANNEL_NOISE:
-                    return toQuantityType(dashboardData.getNoise(), API_NOISE_UNIT);
-                case CHANNEL_PRESSURE:
-                    return toQuantityType(dashboardData.getPressure(), API_PRESSURE_UNIT);
-                case CHANNEL_PRESS_TREND:
-                    return toStringType(dashboardData.getPressureTrend());
-                case CHANNEL_ABSOLUTE_PRESSURE:
-                    return toQuantityType(dashboardData.getAbsolutePressure(), API_PRESSURE_UNIT);
-                case CHANNEL_TIMEUTC:
-                    return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
-                case CHANNEL_DATE_MIN_TEMP:
-                    return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
-                case CHANNEL_DATE_MAX_TEMP:
-                    return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
-                case CHANNEL_HUMIDITY:
-                    return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
-            }
-        }
-        return super.getNAThingProperty(channelId);
-    }
-
-    private @Nullable String toHealthIndexString(@Nullable Integer healthIndex) {
-        if (healthIndex == null) {
-            return null;
-        }
-        switch (healthIndex) {
-            case 0:
-                return "healthy";
-            case 1:
-                return "fine";
-            case 2:
-                return "fair";
-            case 3:
-                return "poor";
-            case 4:
-                return "unhealthy";
-            default:
-                return healthIndex.toString();
-        }
-    }
-
-    @Override
-    protected Optional<Integer> getDataTimestamp() {
-        return getDevice().map(d -> d.getLastStatusStore());
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/presence/NAPresenceCameraHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/presence/NAPresenceCameraHandler.java
deleted file mode 100644 (file)
index 7475e97..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.presence;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.toOnOffType;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.camera.CameraHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-import io.swagger.client.model.NAWelcomeCamera;
-
-/**
- * {@link NAPresenceCameraHandler} is the class used to handle Presence camera data
- *
- * @author Sven Strohschein - Initial contribution
- */
-@NonNullByDefault
-public class NAPresenceCameraHandler extends CameraHandler {
-
-    private static final String FLOODLIGHT_SET_URL_PATH = "/command/floodlight_set_config";
-
-    private State floodlightAutoModeState = UnDefType.UNDEF;
-
-    public NAPresenceCameraHandler(final Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-        String channelId = channelUID.getId();
-        switch (channelId) {
-            case CHANNEL_CAMERA_FLOODLIGHT:
-                if (command == OnOffType.ON) {
-                    switchFloodlight(true);
-                } else if (command == OnOffType.OFF) {
-                    switchFloodlight(false);
-                }
-                break;
-            case CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE:
-                if (command == OnOffType.ON) {
-                    switchFloodlightAutoMode(true);
-                } else if (command == OnOffType.OFF) {
-                    switchFloodlightAutoMode(false);
-                }
-                break;
-        }
-        super.handleCommand(channelUID, command);
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        switch (channelId) {
-            case CHANNEL_CAMERA_FLOODLIGHT:
-                return getFloodlightState();
-            case CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE:
-                // The auto-mode state shouldn't be updated, because this isn't a dedicated information. When the
-                // floodlight is switched on the state within the Netatmo API is "on" and the information if the
-                // previous
-                // state was "auto" instead of "off" is lost... Therefore the binding handles its own auto-mode state.
-                if (floodlightAutoModeState == UnDefType.UNDEF) {
-                    floodlightAutoModeState = getFloodlightAutoModeState();
-                }
-                return floodlightAutoModeState;
-        }
-        return super.getNAThingProperty(channelId);
-    }
-
-    private State getFloodlightState() {
-        return getModule().map(m -> toOnOffType(m.getLightModeStatus() == NAWelcomeCamera.LightModeStatusEnum.ON))
-                .orElse(UnDefType.UNDEF);
-    }
-
-    private State getFloodlightAutoModeState() {
-        return getModule().map(m -> toOnOffType(m.getLightModeStatus() == NAWelcomeCamera.LightModeStatusEnum.AUTO))
-                .orElse(UnDefType.UNDEF);
-    }
-
-    private void switchFloodlight(boolean isOn) {
-        if (isOn) {
-            changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.ON);
-        } else {
-            switchFloodlightAutoMode(floodlightAutoModeState == OnOffType.ON);
-        }
-    }
-
-    private void switchFloodlightAutoMode(boolean isAutoMode) {
-        floodlightAutoModeState = toOnOffType(isAutoMode);
-        if (isAutoMode) {
-            changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.AUTO);
-        } else {
-            changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.OFF);
-        }
-    }
-
-    private void changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum mode) {
-        Optional<String> localCameraURL = getLocalCameraURL();
-        if (localCameraURL.isPresent()) {
-            String url = localCameraURL.get() + FLOODLIGHT_SET_URL_PATH + "?config=%7B%22mode%22:%22" + mode.toString()
-                    + "%22%7D";
-            executeGETRequest(url);
-
-            invalidateParentCacheAndRefresh();
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoChannelTypeProvider.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoChannelTypeProvider.java
new file mode 100644 (file)
index 0000000..42fe411
--- /dev/null
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2010-2022 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.providers;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.core.thing.type.ChannelType;
+import org.openhab.core.thing.type.ChannelTypeBuilder;
+import org.openhab.core.thing.type.ChannelTypeProvider;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.thing.type.StateChannelTypeBuilder;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * Extends the ChannelTypeProvider generating Channel Types based on {@link MeasureClass} enum.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+@Component(service = { NetatmoChannelTypeProvider.class, ChannelTypeProvider.class })
+public class NetatmoChannelTypeProvider implements ChannelTypeProvider {
+    private final Collection<ChannelType> channelTypes = new HashSet<>();
+
+    public NetatmoChannelTypeProvider() {
+        MeasureClass.AS_SET.forEach(mc -> mc.channels.forEach((measureChannel, channelDetails) -> {
+            StateChannelTypeBuilder channelTypeBuilder = ChannelTypeBuilder
+                    .state(new ChannelTypeUID(BINDING_ID, measureChannel), measureChannel.replace("-", " "),
+                            channelDetails.itemType)
+                    .withStateDescriptionFragment(channelDetails.stateDescriptionFragment)
+                    .withConfigDescriptionURI(channelDetails.configURI);
+
+            channelTypes.add(channelTypeBuilder.build());
+        }));
+    }
+
+    @Override
+    public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
+        return Collections.unmodifiableCollection(channelTypes);
+    }
+
+    @Override
+    public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
+        return channelTypes.stream().filter(ct -> ct.getUID().equals(channelTypeUID)).findFirst().orElse(null);
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoDescriptionProvider.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoDescriptionProvider.java
new file mode 100644 (file)
index 0000000..ee1b14f
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2022 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.providers;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.events.EventPublisher;
+import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
+import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
+import org.openhab.core.thing.link.ItemChannelLinkRegistry;
+import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * Dynamic provider of state options while leaving other state description fields as original.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@Component(service = { DynamicStateDescriptionProvider.class, NetatmoDescriptionProvider.class })
+@NonNullByDefault
+public class NetatmoDescriptionProvider extends BaseDynamicStateDescriptionProvider {
+    @Activate
+    public NetatmoDescriptionProvider(final @Reference EventPublisher eventPublisher,
+            final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
+            final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
+        this.eventPublisher = eventPublisher;
+        this.itemChannelLinkRegistry = itemChannelLinkRegistry;
+        this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoThingTypeProvider.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoThingTypeProvider.java
new file mode 100644 (file)
index 0000000..0e81eb6
--- /dev/null
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2010-2022 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.providers;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.ThingTypeProvider;
+import org.openhab.core.thing.i18n.ThingTypeI18nLocalizationService;
+import org.openhab.core.thing.type.ChannelGroupDefinition;
+import org.openhab.core.thing.type.ChannelGroupTypeUID;
+import org.openhab.core.thing.type.ThingType;
+import org.openhab.core.thing.type.ThingTypeBuilder;
+import org.osgi.framework.Bundle;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extends the ThingTypeProvider to generated Thing types based on {@link ModuleType} enum.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@Component(service = ThingTypeProvider.class)
+@NonNullByDefault
+public class NetatmoThingTypeProvider implements ThingTypeProvider {
+    private final Logger logger = LoggerFactory.getLogger(NetatmoThingTypeProvider.class);
+    private final ThingTypeI18nLocalizationService localizationService;
+    private final Bundle bundle;
+
+    @Activate
+    public NetatmoThingTypeProvider(final @Reference ThingTypeI18nLocalizationService localizationService,
+            ComponentContext componentContext) {
+        this.bundle = componentContext.getBundleContext().getBundle();
+        this.localizationService = localizationService;
+    }
+
+    @Override
+    public Collection<ThingType> getThingTypes(@Nullable Locale locale) {
+        return ModuleType.AS_SET.stream().filter(mt -> mt != ModuleType.UNKNOWN)
+                .map(mt -> Optional.ofNullable(getThingType(mt.thingTypeUID, locale))).map(Optional::get)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) {
+        if (BINDING_ID.equalsIgnoreCase(thingTypeUID.getBindingId())) {
+            try {
+                ModuleType moduleType = ModuleType.from(thingTypeUID);
+
+                ThingTypeBuilder thingTypeBuilder = ThingTypeBuilder.instance(thingTypeUID, thingTypeUID.toString())
+                        .withRepresentationProperty(EQUIPMENT_ID).withExtensibleChannelTypeIds(moduleType.extensions)
+                        .withChannelGroupDefinitions(getGroupDefinitions(moduleType))
+                        .withConfigDescriptionURI(moduleType.getConfigDescription());
+
+                ThingTypeUID bridgeType = moduleType.getBridge().thingTypeUID;
+                if (!ModuleType.UNKNOWN.thingTypeUID.equals(bridgeType)) {
+                    thingTypeBuilder.withSupportedBridgeTypeUIDs(List.of(bridgeType.getAsString()));
+                }
+
+                return localizationService.createLocalizedThingType(bundle,
+                        moduleType.isABridge() ? thingTypeBuilder.buildBridge() : thingTypeBuilder.build(), locale);
+            } catch (IllegalArgumentException e) {
+                logger.warn("Unable to define ModuleType for thingType {} : {}", thingTypeUID.getId(), e.getMessage());
+            }
+        }
+        return null;
+    }
+
+    private List<ChannelGroupDefinition> getGroupDefinitions(ModuleType thingType) {
+        return thingType.groupTypes.stream().map(groupTypeName -> new ChannelGroupDefinition(toGroupName(groupTypeName),
+                new ChannelGroupTypeUID(BINDING_ID, groupTypeName))).collect(Collectors.toList());
+    }
+
+    public static String toGroupName(String groupeTypeName) {
+        return groupeTypeName.replace(OPTION_EXTENDED, "").replace(OPTION_OUTSIDE, "");
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAMainHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAMainHandler.java
deleted file mode 100644 (file)
index bf9d1c6..0000000
+++ /dev/null
@@ -1,297 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.station;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.WeatherUtils;
-import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAMain;
-
-/**
- * {@link NAMainHandler} is the base class for all current Netatmo
- * weather station equipments (both modules and devices)
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class NAMainHandler extends NetatmoDeviceHandler<NAMain> {
-    private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
-
-    public NAMainHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected Optional<NAMain> updateReadings() {
-        Optional<NAMain> result = getBridgeHandler().flatMap(handler -> handler.getStationsDataBody(getId()))
-                .map(dataBody -> nonNullStream(dataBody.getDevices())
-                        .filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
-        result.ifPresent(device -> {
-            nonNullList(device.getModules()).forEach(child -> childs.put(child.getId(), child));
-        });
-
-        updateMeasurements();
-
-        childs.keySet().forEach((childId) -> {
-            findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> {
-                naChildModule.updateMeasurements();
-            });
-        });
-
-        return result;
-    }
-
-    @Override
-    protected void updateProperties(NAMain deviceData) {
-        updateProperties(deviceData.getFirmware(), deviceData.getType());
-    }
-
-    @Override
-    public void updateMeasurements() {
-        updateDayMeasurements();
-        updateWeekMeasurements();
-        updateMonthMeasurements();
-    }
-
-    private void updateDayMeasurements() {
-        List<String> channels = new ArrayList<>();
-        List<String> types = new ArrayList<>();
-        addMeasurement(channels, types, CHANNEL_MIN_CO2, MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_MAX_CO2, MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY, MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY, MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_MIN_NOISE, MIN_NOISE);
-        addMeasurement(channels, types, CHANNEL_MAX_NOISE, MAX_NOISE);
-        addMeasurement(channels, types, CHANNEL_MIN_PRESSURE, MIN_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_MAX_PRESSURE, MAX_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2, DATE_MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2, DATE_MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY, DATE_MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY, DATE_MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_NOISE, DATE_MIN_NOISE);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_NOISE, DATE_MAX_NOISE);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_PRESSURE, DATE_MIN_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_PRESSURE, DATE_MAX_PRESSURE);
-        if (!channels.isEmpty()) {
-            getMeasurements(getId(), null, ONE_DAY, types, channels, channelMeasurements);
-        }
-    }
-
-    private void updateWeekMeasurements() {
-        List<String> channels = new ArrayList<>();
-        List<String> types = new ArrayList<>();
-        addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_WEEK, MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_WEEK, MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_WEEK, MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_WEEK, MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_MIN_NOISE_THIS_WEEK, MIN_NOISE);
-        addMeasurement(channels, types, CHANNEL_MAX_NOISE_THIS_WEEK, MAX_NOISE);
-        addMeasurement(channels, types, CHANNEL_MIN_PRESSURE_THIS_WEEK, MIN_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_MAX_PRESSURE_THIS_WEEK, MAX_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_WEEK, MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_WEEK, MAX_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_WEEK, DATE_MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_WEEK, DATE_MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK, DATE_MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK, DATE_MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_NOISE_THIS_WEEK, DATE_MIN_NOISE);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_NOISE_THIS_WEEK, DATE_MAX_NOISE);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_PRESSURE_THIS_WEEK, DATE_MIN_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_PRESSURE_THIS_WEEK, DATE_MAX_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_WEEK, DATE_MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_WEEK, DATE_MAX_TEMP);
-        if (!channels.isEmpty()) {
-            getMeasurements(getId(), null, ONE_WEEK, types, channels, channelMeasurements);
-        }
-    }
-
-    private void updateMonthMeasurements() {
-        List<String> channels = new ArrayList<>();
-        List<String> types = new ArrayList<>();
-        addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_MONTH, MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_MONTH, MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_MONTH, MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_MONTH, MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_MIN_NOISE_THIS_MONTH, MIN_NOISE);
-        addMeasurement(channels, types, CHANNEL_MAX_NOISE_THIS_MONTH, MAX_NOISE);
-        addMeasurement(channels, types, CHANNEL_MIN_PRESSURE_THIS_MONTH, MIN_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_MAX_PRESSURE_THIS_MONTH, MAX_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_MONTH, MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_MONTH, MAX_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_MONTH, DATE_MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_MONTH, DATE_MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH, DATE_MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH, DATE_MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_NOISE_THIS_MONTH, DATE_MIN_NOISE);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_NOISE_THIS_MONTH, DATE_MAX_NOISE);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_PRESSURE_THIS_MONTH, DATE_MIN_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_PRESSURE_THIS_MONTH, DATE_MAX_PRESSURE);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_MONTH, DATE_MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_MONTH, DATE_MAX_TEMP);
-        if (!channels.isEmpty()) {
-            getMeasurements(getId(), null, ONE_MONTH, types, channels, channelMeasurements);
-        }
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        NADashboardData dashboardData = getDevice().map(d -> d.getDashboardData()).orElse(null);
-        if (dashboardData != null) {
-            switch (channelId) {
-                case CHANNEL_CO2:
-                    return toQuantityType(dashboardData.getCo2(), API_CO2_UNIT);
-                case CHANNEL_TEMPERATURE:
-                    return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
-                case CHANNEL_MIN_TEMP:
-                    return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
-                case CHANNEL_MAX_TEMP:
-                    return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
-                case CHANNEL_TEMP_TREND:
-                    return toStringType(dashboardData.getTempTrend());
-                case CHANNEL_NOISE:
-                    return toQuantityType(dashboardData.getNoise(), API_NOISE_UNIT);
-                case CHANNEL_PRESSURE:
-                    return toQuantityType(dashboardData.getPressure(), API_PRESSURE_UNIT);
-                case CHANNEL_PRESS_TREND:
-                    return toStringType(dashboardData.getPressureTrend());
-                case CHANNEL_ABSOLUTE_PRESSURE:
-                    return toQuantityType(dashboardData.getAbsolutePressure(), API_PRESSURE_UNIT);
-                case CHANNEL_TIMEUTC:
-                    return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
-                case CHANNEL_DATE_MIN_TEMP:
-                    return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
-                case CHANNEL_DATE_MAX_TEMP:
-                    return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
-                case CHANNEL_HUMIDITY:
-                    return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
-                case CHANNEL_HUMIDEX:
-                    return toDecimalType(
-                            WeatherUtils.getHumidex(dashboardData.getTemperature(), dashboardData.getHumidity()));
-                case CHANNEL_HEATINDEX:
-                    return toQuantityType(
-                            WeatherUtils.getHeatIndex(dashboardData.getTemperature(), dashboardData.getHumidity()),
-                            API_TEMPERATURE_UNIT);
-                case CHANNEL_DEWPOINT:
-                    return toQuantityType(
-                            WeatherUtils.getDewPoint(dashboardData.getTemperature(), dashboardData.getHumidity()),
-                            API_TEMPERATURE_UNIT);
-                case CHANNEL_DEWPOINTDEP:
-                    Double dewPoint = WeatherUtils.getDewPoint(dashboardData.getTemperature(),
-                            dashboardData.getHumidity());
-                    return toQuantityType(WeatherUtils.getDewPointDep(dashboardData.getTemperature(), dewPoint),
-                            API_TEMPERATURE_UNIT);
-            }
-        }
-
-        switch (channelId) {
-            case CHANNEL_MIN_CO2:
-            case CHANNEL_MIN_CO2_THIS_WEEK:
-            case CHANNEL_MIN_CO2_THIS_MONTH:
-            case CHANNEL_MAX_CO2:
-            case CHANNEL_MAX_CO2_THIS_WEEK:
-            case CHANNEL_MAX_CO2_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_CO2_UNIT);
-            case CHANNEL_MIN_HUMIDITY:
-            case CHANNEL_MIN_HUMIDITY_THIS_WEEK:
-            case CHANNEL_MIN_HUMIDITY_THIS_MONTH:
-            case CHANNEL_MAX_HUMIDITY:
-            case CHANNEL_MAX_HUMIDITY_THIS_WEEK:
-            case CHANNEL_MAX_HUMIDITY_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_HUMIDITY_UNIT);
-            case CHANNEL_MIN_NOISE:
-            case CHANNEL_MIN_NOISE_THIS_WEEK:
-            case CHANNEL_MIN_NOISE_THIS_MONTH:
-            case CHANNEL_MAX_NOISE:
-            case CHANNEL_MAX_NOISE_THIS_WEEK:
-            case CHANNEL_MAX_NOISE_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_NOISE_UNIT);
-            case CHANNEL_MIN_PRESSURE:
-            case CHANNEL_MIN_PRESSURE_THIS_WEEK:
-            case CHANNEL_MIN_PRESSURE_THIS_MONTH:
-            case CHANNEL_MAX_PRESSURE:
-            case CHANNEL_MAX_PRESSURE_THIS_WEEK:
-            case CHANNEL_MAX_PRESSURE_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_PRESSURE_UNIT);
-            case CHANNEL_MIN_TEMP_THIS_WEEK:
-            case CHANNEL_MIN_TEMP_THIS_MONTH:
-            case CHANNEL_MAX_TEMP_THIS_WEEK:
-            case CHANNEL_MAX_TEMP_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_TEMPERATURE_UNIT);
-            case CHANNEL_DATE_MIN_CO2:
-            case CHANNEL_DATE_MIN_CO2_THIS_WEEK:
-            case CHANNEL_DATE_MIN_CO2_THIS_MONTH:
-            case CHANNEL_DATE_MAX_CO2:
-            case CHANNEL_DATE_MAX_CO2_THIS_WEEK:
-            case CHANNEL_DATE_MAX_CO2_THIS_MONTH:
-            case CHANNEL_DATE_MIN_NOISE:
-            case CHANNEL_DATE_MIN_NOISE_THIS_WEEK:
-            case CHANNEL_DATE_MIN_NOISE_THIS_MONTH:
-            case CHANNEL_DATE_MAX_NOISE:
-            case CHANNEL_DATE_MAX_NOISE_THIS_WEEK:
-            case CHANNEL_DATE_MAX_NOISE_THIS_MONTH:
-            case CHANNEL_DATE_MIN_HUMIDITY:
-            case CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK:
-            case CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH:
-            case CHANNEL_DATE_MAX_HUMIDITY:
-            case CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK:
-            case CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH:
-            case CHANNEL_DATE_MIN_PRESSURE:
-            case CHANNEL_DATE_MIN_PRESSURE_THIS_WEEK:
-            case CHANNEL_DATE_MIN_PRESSURE_THIS_MONTH:
-            case CHANNEL_DATE_MAX_PRESSURE:
-            case CHANNEL_DATE_MAX_PRESSURE_THIS_WEEK:
-            case CHANNEL_DATE_MAX_PRESSURE_THIS_MONTH:
-            case CHANNEL_DATE_MIN_TEMP_THIS_WEEK:
-            case CHANNEL_DATE_MIN_TEMP_THIS_MONTH:
-            case CHANNEL_DATE_MAX_TEMP_THIS_WEEK:
-            case CHANNEL_DATE_MAX_TEMP_THIS_MONTH:
-                return toDateTimeType(channelMeasurements.get(channelId), timeZoneProvider.getTimeZone());
-        }
-
-        return super.getNAThingProperty(channelId);
-    }
-
-    @Override
-    protected Optional<Integer> getDataTimestamp() {
-        return getDevice().map(d -> d.getLastStatusStore());
-    }
-
-    @Override
-    protected boolean isReachable() {
-        boolean result = false;
-        Optional<NAMain> device = getDevice();
-        if (device.isPresent()) {
-            Boolean reachable = device.get().isReachable();
-            result = reachable != null ? reachable.booleanValue() : false;
-        }
-        return result;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule1Handler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule1Handler.java
deleted file mode 100644 (file)
index 0279b66..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.station;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.WeatherUtils;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAStationModule;
-
-/**
- * {@link NAModule1Handler} is the class used to handle the outdoor module
- * capable of reporting temperature and humidity
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class NAModule1Handler extends NetatmoModuleHandler<NAStationModule> {
-    private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
-
-    public NAModule1Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected void updateProperties(NAStationModule moduleData) {
-        updateProperties(moduleData.getFirmware(), moduleData.getType());
-    }
-
-    @Override
-    public void updateMeasurements() {
-        updateDayMeasurements();
-        updateWeekMeasurements();
-        updateMonthMeasurements();
-    }
-
-    private void updateDayMeasurements() {
-        List<String> channels = new ArrayList<>();
-        List<String> types = new ArrayList<>();
-        addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY, MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY, MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY, DATE_MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY, DATE_MAX_HUM);
-        if (!channels.isEmpty()) {
-            getMeasurements(getParentId(), getId(), ONE_DAY, types, channels, channelMeasurements);
-        }
-    }
-
-    private void updateWeekMeasurements() {
-        List<String> channels = new ArrayList<>();
-        List<String> types = new ArrayList<>();
-        addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_WEEK, MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_WEEK, MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_WEEK, MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_WEEK, MAX_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK, DATE_MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK, DATE_MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_WEEK, DATE_MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_WEEK, DATE_MAX_TEMP);
-        if (!channels.isEmpty()) {
-            getMeasurements(getParentId(), getId(), ONE_WEEK, types, channels, channelMeasurements);
-        }
-    }
-
-    private void updateMonthMeasurements() {
-        List<String> channels = new ArrayList<>();
-        List<String> types = new ArrayList<>();
-        addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_MONTH, MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_MONTH, MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_MONTH, MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_MONTH, MAX_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH, DATE_MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH, DATE_MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_MONTH, DATE_MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_MONTH, DATE_MAX_TEMP);
-        if (!channels.isEmpty()) {
-            getMeasurements(getParentId(), getId(), ONE_MONTH, types, channels, channelMeasurements);
-        }
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
-        if (dashboardData != null) {
-            switch (channelId) {
-                case CHANNEL_TEMP_TREND:
-                    return toStringType(dashboardData.getTempTrend());
-                case CHANNEL_TEMPERATURE:
-                    return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
-                case CHANNEL_DATE_MIN_TEMP:
-                    return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
-                case CHANNEL_DATE_MAX_TEMP:
-                    return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
-                case CHANNEL_MIN_TEMP:
-                    return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
-                case CHANNEL_MAX_TEMP:
-                    return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
-                case CHANNEL_HUMIDITY:
-                    return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
-                case CHANNEL_TIMEUTC:
-                    return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
-                case CHANNEL_HUMIDEX:
-                    return toDecimalType(
-                            WeatherUtils.getHumidex(dashboardData.getTemperature(), dashboardData.getHumidity()));
-                case CHANNEL_HEATINDEX:
-                    return toQuantityType(
-                            WeatherUtils.getHeatIndex(dashboardData.getTemperature(), dashboardData.getHumidity()),
-                            API_TEMPERATURE_UNIT);
-                case CHANNEL_DEWPOINT:
-                    return toQuantityType(
-                            WeatherUtils.getDewPoint(dashboardData.getTemperature(), dashboardData.getHumidity()),
-                            API_TEMPERATURE_UNIT);
-                case CHANNEL_DEWPOINTDEP:
-                    Double dewpoint = WeatherUtils.getDewPoint(dashboardData.getTemperature(),
-                            dashboardData.getHumidity());
-                    return toQuantityType(WeatherUtils.getDewPointDep(dashboardData.getTemperature(), dewpoint),
-                            API_TEMPERATURE_UNIT);
-            }
-        }
-
-        switch (channelId) {
-            case CHANNEL_MIN_HUMIDITY:
-            case CHANNEL_MIN_HUMIDITY_THIS_WEEK:
-            case CHANNEL_MIN_HUMIDITY_THIS_MONTH:
-            case CHANNEL_MAX_HUMIDITY:
-            case CHANNEL_MAX_HUMIDITY_THIS_WEEK:
-            case CHANNEL_MAX_HUMIDITY_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_HUMIDITY_UNIT);
-            case CHANNEL_MIN_TEMP_THIS_WEEK:
-            case CHANNEL_MIN_TEMP_THIS_MONTH:
-            case CHANNEL_MAX_TEMP_THIS_WEEK:
-            case CHANNEL_MAX_TEMP_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_TEMPERATURE_UNIT);
-            case CHANNEL_DATE_MIN_HUMIDITY:
-            case CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK:
-            case CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH:
-            case CHANNEL_DATE_MAX_HUMIDITY:
-            case CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK:
-            case CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH:
-            case CHANNEL_DATE_MIN_TEMP_THIS_WEEK:
-            case CHANNEL_DATE_MIN_TEMP_THIS_MONTH:
-            case CHANNEL_DATE_MAX_TEMP_THIS_WEEK:
-            case CHANNEL_DATE_MAX_TEMP_THIS_MONTH:
-                return toDateTimeType(channelMeasurements.get(channelId), timeZoneProvider.getTimeZone());
-        }
-
-        return super.getNAThingProperty(channelId);
-    }
-
-    @Override
-    protected boolean isReachable() {
-        boolean result = false;
-        Optional<NAStationModule> module = getModule();
-        if (module.isPresent()) {
-            Boolean reachable = module.get().isReachable();
-            result = reachable != null ? reachable.booleanValue() : false;
-        }
-        return result;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule2Handler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule2Handler.java
deleted file mode 100644 (file)
index 82f8ffe..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.station;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAStationModule;
-
-/**
- * {@link NAModule2Handler} is the class used to handle the wind module
- * capable of reporting wind angle and strength
- *
- * @author Gaël L'hopital - Initial contribution
- */
-@NonNullByDefault
-public class NAModule2Handler extends NetatmoModuleHandler<NAStationModule> {
-
-    public NAModule2Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected void updateProperties(NAStationModule moduleData) {
-        updateProperties(moduleData.getFirmware(), moduleData.getType());
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
-        if (dashboardData != null) {
-            switch (channelId) {
-                case CHANNEL_WIND_ANGLE:
-                    return toQuantityType(dashboardData.getWindAngle(), API_WIND_DIRECTION_UNIT);
-                case CHANNEL_WIND_STRENGTH:
-                    return toQuantityType(dashboardData.getWindStrength(), API_WIND_SPEED_UNIT);
-                case CHANNEL_GUST_ANGLE:
-                    return toQuantityType(dashboardData.getGustAngle(), API_WIND_DIRECTION_UNIT);
-                case CHANNEL_GUST_STRENGTH:
-                    return toQuantityType(dashboardData.getGustStrength(), API_WIND_SPEED_UNIT);
-                case CHANNEL_TIMEUTC:
-                    return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
-                case CHANNEL_MAX_WIND_STRENGTH:
-                    return toQuantityType(dashboardData.getMaxWindStr(), API_WIND_SPEED_UNIT);
-                case CHANNEL_DATE_MAX_WIND_STRENGTH:
-                    return toDateTimeType(dashboardData.getDateMaxWindStr(), timeZoneProvider.getTimeZone());
-            }
-        }
-        return super.getNAThingProperty(channelId);
-    }
-
-    @Override
-    protected boolean isReachable() {
-        boolean result = false;
-        Optional<NAStationModule> module = getModule();
-        if (module.isPresent()) {
-            Boolean reachable = module.get().isReachable();
-            result = reachable != null ? reachable.booleanValue() : false;
-        }
-        return result;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule3Handler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule3Handler.java
deleted file mode 100644 (file)
index 654d552..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.station;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAStationModule;
-
-/**
- * {@link NAModule3Handler} is the class used to handle the Rain Gauge
- * capable of measuring precipitation
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class NAModule3Handler extends NetatmoModuleHandler<NAStationModule> {
-    private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
-
-    public NAModule3Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected void updateProperties(NAStationModule moduleData) {
-        updateProperties(moduleData.getFirmware(), moduleData.getType());
-    }
-
-    @Override
-    public void updateMeasurements() {
-        List<String> types = Arrays.asList(SUM_RAIN);
-
-        if (isLinked(CHANNEL_SUM_RAIN_THIS_WEEK)) {
-            getMeasurements(getParentId(), getId(), ONE_WEEK, types, Arrays.asList(CHANNEL_SUM_RAIN_THIS_WEEK),
-                    channelMeasurements);
-        }
-
-        if (isLinked(CHANNEL_SUM_RAIN_THIS_MONTH)) {
-            getMeasurements(getParentId(), getId(), ONE_MONTH, types, Arrays.asList(CHANNEL_SUM_RAIN_THIS_MONTH),
-                    channelMeasurements);
-        }
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
-        if (dashboardData != null) {
-            switch (channelId) {
-                case CHANNEL_RAIN:
-                    return toQuantityType(dashboardData.getRain(), API_RAIN_UNIT);
-                case CHANNEL_SUM_RAIN1:
-                    return toQuantityType(dashboardData.getSumRain1(), API_RAIN_UNIT);
-                case CHANNEL_SUM_RAIN24:
-                    return toQuantityType(dashboardData.getSumRain24(), API_RAIN_UNIT);
-                case CHANNEL_TIMEUTC:
-                    return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
-            }
-        }
-
-        switch (channelId) {
-            case CHANNEL_SUM_RAIN_THIS_WEEK:
-            case CHANNEL_SUM_RAIN_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_RAIN_UNIT);
-        }
-
-        return super.getNAThingProperty(channelId);
-    }
-
-    @Override
-    protected boolean isReachable() {
-        boolean result = false;
-        Optional<NAStationModule> module = getModule();
-        if (module.isPresent()) {
-            Boolean reachable = module.get().isReachable();
-            result = reachable != null ? reachable.booleanValue() : false;
-        }
-        return result;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule4Handler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule4Handler.java
deleted file mode 100644 (file)
index 6699b6a..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.station;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.WeatherUtils;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAStationModule;
-
-/**
- * {@link NAModule4Handler} is the class used to handle the additional
- * indoor module capable of reporting temperature, humidity and CO2 level
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class NAModule4Handler extends NetatmoModuleHandler<NAStationModule> {
-    private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
-
-    public NAModule4Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected void updateProperties(NAStationModule moduleData) {
-        updateProperties(moduleData.getFirmware(), moduleData.getType());
-    }
-
-    @Override
-    public void updateMeasurements() {
-        updateDayMeasurements();
-        updateWeekMeasurements();
-        updateMonthMeasurements();
-    }
-
-    private void updateDayMeasurements() {
-        List<String> channels = new ArrayList<>();
-        List<String> types = new ArrayList<>();
-        addMeasurement(channels, types, CHANNEL_MIN_CO2, MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_MAX_CO2, MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY, MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY, MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2, DATE_MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2, DATE_MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY, DATE_MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY, DATE_MAX_HUM);
-        if (!channels.isEmpty()) {
-            getMeasurements(getParentId(), getId(), ONE_DAY, types, channels, channelMeasurements);
-        }
-    }
-
-    private void updateWeekMeasurements() {
-        List<String> channels = new ArrayList<>();
-        List<String> types = new ArrayList<>();
-        addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_WEEK, MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_WEEK, MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_WEEK, MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_WEEK, MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_WEEK, MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_WEEK, MAX_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_WEEK, DATE_MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_WEEK, DATE_MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK, DATE_MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK, DATE_MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_WEEK, DATE_MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_WEEK, DATE_MAX_TEMP);
-        if (!channels.isEmpty()) {
-            getMeasurements(getParentId(), getId(), ONE_WEEK, types, channels, channelMeasurements);
-        }
-    }
-
-    private void updateMonthMeasurements() {
-        List<String> channels = new ArrayList<>();
-        List<String> types = new ArrayList<>();
-        addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_MONTH, MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_MONTH, MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_MONTH, MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_MONTH, MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_MONTH, MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_MONTH, MAX_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_MONTH, DATE_MIN_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_MONTH, DATE_MAX_CO2);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH, DATE_MIN_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH, DATE_MAX_HUM);
-        addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_MONTH, DATE_MIN_TEMP);
-        addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_MONTH, DATE_MAX_TEMP);
-        if (!channels.isEmpty()) {
-            getMeasurements(getParentId(), getId(), ONE_MONTH, types, channels, channelMeasurements);
-        }
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
-        if (dashboardData != null) {
-            switch (channelId) {
-                case CHANNEL_TEMP_TREND:
-                    return toStringType(dashboardData.getTempTrend());
-                case CHANNEL_CO2:
-                    return toQuantityType(dashboardData.getCo2(), API_CO2_UNIT);
-                case CHANNEL_TEMPERATURE:
-                    return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
-                case CHANNEL_DATE_MIN_TEMP:
-                    return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
-                case CHANNEL_DATE_MAX_TEMP:
-                    return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
-                case CHANNEL_MIN_TEMP:
-                    return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
-                case CHANNEL_MAX_TEMP:
-                    return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
-                case CHANNEL_TIMEUTC:
-                    return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
-                case CHANNEL_HUMIDITY:
-                    return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
-                case CHANNEL_HUMIDEX:
-                    return toDecimalType(
-                            WeatherUtils.getHumidex(dashboardData.getTemperature(), dashboardData.getHumidity()));
-                case CHANNEL_HEATINDEX:
-                    return toQuantityType(
-                            WeatherUtils.getHeatIndex(dashboardData.getTemperature(), dashboardData.getHumidity()),
-                            API_TEMPERATURE_UNIT);
-                case CHANNEL_DEWPOINT:
-                    return toQuantityType(
-                            WeatherUtils.getDewPoint(dashboardData.getTemperature(), dashboardData.getHumidity()),
-                            API_TEMPERATURE_UNIT);
-                case CHANNEL_DEWPOINTDEP:
-                    Double dewpoint = WeatherUtils.getDewPoint(dashboardData.getTemperature(),
-                            dashboardData.getHumidity());
-                    return toQuantityType(WeatherUtils.getDewPointDep(dashboardData.getTemperature(), dewpoint),
-                            API_TEMPERATURE_UNIT);
-            }
-        }
-
-        switch (channelId) {
-            case CHANNEL_MIN_CO2:
-            case CHANNEL_MIN_CO2_THIS_WEEK:
-            case CHANNEL_MIN_CO2_THIS_MONTH:
-            case CHANNEL_MAX_CO2:
-            case CHANNEL_MAX_CO2_THIS_WEEK:
-            case CHANNEL_MAX_CO2_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_CO2_UNIT);
-            case CHANNEL_MIN_HUMIDITY:
-            case CHANNEL_MIN_HUMIDITY_THIS_WEEK:
-            case CHANNEL_MIN_HUMIDITY_THIS_MONTH:
-            case CHANNEL_MAX_HUMIDITY:
-            case CHANNEL_MAX_HUMIDITY_THIS_WEEK:
-            case CHANNEL_MAX_HUMIDITY_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_HUMIDITY_UNIT);
-            case CHANNEL_MIN_TEMP_THIS_WEEK:
-            case CHANNEL_MIN_TEMP_THIS_MONTH:
-            case CHANNEL_MAX_TEMP_THIS_WEEK:
-            case CHANNEL_MAX_TEMP_THIS_MONTH:
-                return toQuantityType(channelMeasurements.get(channelId), API_TEMPERATURE_UNIT);
-            case CHANNEL_DATE_MIN_CO2:
-            case CHANNEL_DATE_MIN_CO2_THIS_WEEK:
-            case CHANNEL_DATE_MIN_CO2_THIS_MONTH:
-            case CHANNEL_DATE_MAX_CO2:
-            case CHANNEL_DATE_MAX_CO2_THIS_WEEK:
-            case CHANNEL_DATE_MAX_CO2_THIS_MONTH:
-            case CHANNEL_DATE_MIN_HUMIDITY:
-            case CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK:
-            case CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH:
-            case CHANNEL_DATE_MAX_HUMIDITY:
-            case CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK:
-            case CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH:
-            case CHANNEL_DATE_MIN_TEMP_THIS_WEEK:
-            case CHANNEL_DATE_MIN_TEMP_THIS_MONTH:
-            case CHANNEL_DATE_MAX_TEMP_THIS_WEEK:
-            case CHANNEL_DATE_MAX_TEMP_THIS_MONTH:
-                return toDateTimeType(channelMeasurements.get(channelId), timeZoneProvider.getTimeZone());
-        }
-
-        return super.getNAThingProperty(channelId);
-    }
-
-    @Override
-    protected boolean isReachable() {
-        boolean result = false;
-        Optional<NAStationModule> module = getModule();
-        if (module.isPresent()) {
-            Boolean reachable = module.get().isReachable();
-            result = reachable != null ? reachable.booleanValue() : false;
-        }
-        return result;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/thermostat/NAPlugHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/thermostat/NAPlugHandler.java
deleted file mode 100644 (file)
index 66b1ea2..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.thermostat;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.time.ZonedDateTime;
-import java.time.temporal.TemporalAdjusters;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-import io.swagger.client.model.NAPlug;
-import io.swagger.client.model.NAYearMonth;
-
-/**
- * {@link NAPlugHandler} is the class used to handle the plug
- * device of a thermostat set
- *
- * @author Gaël L'hopital - Initial contribution OH2 version
- *
- */
-@NonNullByDefault
-public class NAPlugHandler extends NetatmoDeviceHandler<NAPlug> {
-
-    public NAPlugHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected Optional<NAPlug> updateReadings() {
-        Optional<NAPlug> result = getBridgeHandler().flatMap(handler -> handler.getThermostatsDataBody(getId()))
-                .map(dataBody -> nonNullStream(dataBody.getDevices())
-                        .filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
-        result.ifPresent(device -> {
-            nonNullList(device.getModules()).forEach(child -> childs.put(child.getId(), child));
-        });
-        return result;
-    }
-
-    @Override
-    protected void updateProperties(NAPlug deviceData) {
-        updateProperties(deviceData.getFirmware(), deviceData.getType());
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        switch (channelId) {
-            case CHANNEL_CONNECTED_BOILER:
-                return getDevice().map(d -> toOnOffType(d.getPlugConnectedBoiler())).orElse(UnDefType.UNDEF);
-            case CHANNEL_LAST_PLUG_SEEN:
-                return getDevice().map(d -> toDateTimeType(d.getLastPlugSeen(), timeZoneProvider.getTimeZone()))
-                        .orElse(UnDefType.UNDEF);
-            case CHANNEL_LAST_BILAN:
-                return toDateTimeType(getLastBilan());
-        }
-        return super.getNAThingProperty(channelId);
-    }
-
-    public @Nullable ZonedDateTime getLastBilan() {
-        Optional<NAYearMonth> lastBilan = getDevice().map(d -> d.getLastBilan());
-        if (lastBilan.isPresent()) {
-            ZonedDateTime zonedDT = ZonedDateTime.of(lastBilan.get().getY(), lastBilan.get().getM(), 1, 0, 0, 0, 0,
-                    ZonedDateTime.now().getZone());
-            return zonedDT.with(TemporalAdjusters.lastDayOfMonth());
-        }
-        return null;
-    }
-
-    @Override
-    protected Optional<Integer> getDataTimestamp() {
-        return getDevice().map(d -> d.getLastStatusStore());
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/thermostat/NATherm1Handler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/thermostat/NATherm1Handler.java
deleted file mode 100644 (file)
index 748cb93..0000000
+++ /dev/null
@@ -1,320 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.thermostat;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-import javax.measure.quantity.Temperature;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.NATherm1StateDescriptionProvider;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.StateOption;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.swagger.client.api.ThermostatApi;
-import io.swagger.client.model.NASetpoint;
-import io.swagger.client.model.NAThermProgram;
-import io.swagger.client.model.NAThermostat;
-import io.swagger.client.model.NATimeTableItem;
-import io.swagger.client.model.NAZone;
-
-/**
- * {@link NATherm1Handler} is the class used to handle the thermostat
- * module of a thermostat set
- *
- * @author Gaël L'hopital - Initial contribution OH2 version
- *
- */
-@NonNullByDefault
-public class NATherm1Handler extends NetatmoModuleHandler<NAThermostat> {
-    private final Logger logger = LoggerFactory.getLogger(NATherm1Handler.class);
-    private final NATherm1StateDescriptionProvider stateDescriptionProvider;
-
-    public NATherm1Handler(Thing thing, NATherm1StateDescriptionProvider stateDescriptionProvider,
-            final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-        this.stateDescriptionProvider = stateDescriptionProvider;
-    }
-
-    @Override
-    protected void updateProperties(NAThermostat moduleData) {
-        updateProperties(moduleData.getFirmware(), moduleData.getType());
-    }
-
-    @Override
-    public void updateChannels(Object moduleObject) {
-        super.updateChannels(moduleObject);
-        getModule().ifPresent(this::updateStateDescription);
-    }
-
-    private void updateStateDescription(NAThermostat thermostat) {
-        List<StateOption> options = new ArrayList<>();
-        for (NAThermProgram planning : nonNullList(thermostat.getThermProgramList())) {
-            options.add(new StateOption(planning.getProgramId(), planning.getName()));
-        }
-        stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_PLANNING), options);
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        Optional<NAThermostat> thermostat = getModule();
-        switch (channelId) {
-            case CHANNEL_THERM_ORIENTATION:
-                return thermostat.map(m -> toDecimalType(m.getThermOrientation())).orElse(UnDefType.UNDEF);
-            case CHANNEL_THERM_RELAY:
-                return thermostat.map(m -> m.getThermRelayCmd() == 100 ? (State) OnOffType.ON : (State) OnOffType.OFF)
-                        .orElse(UnDefType.UNDEF);
-            case CHANNEL_TEMPERATURE:
-                return thermostat.map(m -> toQuantityType(m.getMeasured().getTemperature(), API_TEMPERATURE_UNIT))
-                        .orElse(UnDefType.UNDEF);
-            case CHANNEL_SETPOINT_TEMP:
-                return getCurrentSetpoint();
-            case CHANNEL_TIMEUTC:
-                return thermostat.map(m -> toDateTimeType(m.getMeasured().getTime(), timeZoneProvider.getTimeZone()))
-                        .orElse(UnDefType.UNDEF);
-            case CHANNEL_SETPOINT_END_TIME: {
-                if (thermostat.isPresent()) {
-                    NASetpoint setpoint = thermostat.get().getSetpoint();
-                    if (setpoint != null) {
-                        Integer endTime = setpoint.getSetpointEndtime();
-                        if (endTime == null) {
-                            endTime = getNextProgramTime(nonNullList(thermostat.get().getThermProgramList()));
-                        }
-                        return toDateTimeType(endTime, timeZoneProvider.getTimeZone());
-                    }
-                    return UnDefType.NULL;
-                }
-                return UnDefType.UNDEF;
-            }
-            case CHANNEL_SETPOINT_MODE:
-                return getSetpoint();
-            case CHANNEL_PLANNING: {
-                String currentPlanning = "-";
-                if (thermostat.isPresent()) {
-                    for (NAThermProgram program : nonNullList(thermostat.get().getThermProgramList())) {
-                        if (Boolean.TRUE.equals(program.isSelected())) {
-                            currentPlanning = program.getProgramId();
-                        }
-                    }
-                    return toStringType(currentPlanning);
-                }
-                return UnDefType.UNDEF;
-            }
-        }
-        return super.getNAThingProperty(channelId);
-    }
-
-    private State getSetpoint() {
-        return getModule()
-                .map(m -> m.getSetpoint() != null ? toStringType(m.getSetpoint().getSetpointMode()) : UnDefType.NULL)
-                .orElse(UnDefType.UNDEF);
-    }
-
-    private State getCurrentSetpoint() {
-        Optional<NAThermostat> thermostat = getModule();
-        if (thermostat.isPresent()) {
-            NASetpoint setPoint = thermostat.get().getSetpoint();
-            if (setPoint != null) {
-                String currentMode = setPoint.getSetpointMode();
-
-                NAThermProgram currentProgram = nonNullStream(thermostat.get().getThermProgramList())
-                        .filter(p -> p.isSelected() != null && p.isSelected()).findFirst().get();
-                switch (currentMode) {
-                    case CHANNEL_SETPOINT_MODE_MANUAL:
-                        return toDecimalType(setPoint.getSetpointTemp());
-                    case CHANNEL_SETPOINT_MODE_AWAY:
-                        NAZone zone = getZone(currentProgram.getZones(), 2);
-                        return toDecimalType(zone.getTemp());
-                    case CHANNEL_SETPOINT_MODE_HG:
-                        NAZone zone1 = getZone(currentProgram.getZones(), 3);
-                        return toDecimalType(zone1.getTemp());
-                    case CHANNEL_SETPOINT_MODE_PROGRAM:
-                        NATimeTableItem currentProgramMode = getCurrentProgramMode(
-                                nonNullList(thermostat.get().getThermProgramList()));
-                        if (currentProgramMode != null) {
-                            NAZone zone2 = getZone(currentProgram.getZones(), currentProgramMode.getId());
-                            return toDecimalType(zone2.getTemp());
-                        }
-                    case CHANNEL_SETPOINT_MODE_OFF:
-                    case CHANNEL_SETPOINT_MODE_MAX:
-                        return UnDefType.UNDEF;
-                }
-            }
-        }
-        return UnDefType.NULL;
-    }
-
-    private NAZone getZone(List<NAZone> zones, int searchedId) {
-        return nonNullStream(zones).filter(z -> z.getId() == searchedId).findFirst().get();
-    }
-
-    private long getNetatmoProgramBaseTime() {
-        Calendar mondayZero = Calendar.getInstance();
-        mondayZero.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
-        mondayZero.set(Calendar.HOUR_OF_DAY, 0);
-        mondayZero.set(Calendar.MINUTE, 0);
-        mondayZero.set(Calendar.SECOND, 0);
-        return mondayZero.getTimeInMillis();
-    }
-
-    private @Nullable NATimeTableItem getCurrentProgramMode(List<NAThermProgram> thermProgramList) {
-        NATimeTableItem lastProgram = null;
-        Calendar now = Calendar.getInstance();
-        long diff = (now.getTimeInMillis() - getNetatmoProgramBaseTime()) / 1000 / 60;
-
-        Optional<NAThermProgram> currentProgram = thermProgramList.stream()
-                .filter(p -> p.isSelected() != null && p.isSelected()).findFirst();
-
-        if (currentProgram.isPresent()) {
-            Stream<NATimeTableItem> pastPrograms = nonNullStream(currentProgram.get().getTimetable())
-                    .filter(t -> t.getMOffset() < diff);
-            Optional<NATimeTableItem> program = pastPrograms.reduce((first, second) -> second);
-            if (program.isPresent()) {
-                lastProgram = program.get();
-            }
-        }
-
-        return lastProgram;
-    }
-
-    private int getNextProgramTime(List<NAThermProgram> thermProgramList) {
-        Calendar now = Calendar.getInstance();
-        long diff = (now.getTimeInMillis() - getNetatmoProgramBaseTime()) / 1000 / 60;
-
-        int result = -1;
-
-        for (NAThermProgram thermProgram : thermProgramList) {
-            if (thermProgram.isSelected() != null && thermProgram.isSelected()) {
-                // By default we'll use the first slot of next week - this case will be true if
-                // we are in the last schedule of the week so below loop will not exit by break
-                List<NATimeTableItem> timetable = thermProgram.getTimetable();
-                int next = timetable.get(0).getMOffset() + (7 * 24 * 60);
-
-                for (NATimeTableItem timeTable : timetable) {
-                    if (timeTable.getMOffset() > diff) {
-                        next = timeTable.getMOffset();
-                        break;
-                    }
-                }
-
-                result = (int) (next * 60 + (getNetatmoProgramBaseTime() / 1000));
-            }
-        }
-        return result;
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-        super.handleCommand(channelUID, command);
-        if (!(command instanceof RefreshType)) {
-            try {
-                switch (channelUID.getId()) {
-                    case CHANNEL_SETPOINT_MODE: {
-                        String targetMode = command.toString();
-                        if (CHANNEL_SETPOINT_MODE_MANUAL.equals(targetMode)) {
-                            logger.info(
-                                    "Switching to manual mode is done by assigning a setpoint temperature - command dropped");
-                            updateState(channelUID, getSetpoint());
-                        } else {
-                            pushSetpointUpdate(targetMode, null, null);
-                        }
-                        break;
-                    }
-                    case CHANNEL_SETPOINT_TEMP: {
-                        BigDecimal spTemp = null;
-                        if (command instanceof QuantityType) {
-                            @SuppressWarnings("unchecked")
-                            QuantityType<Temperature> quantity = ((QuantityType<Temperature>) command)
-                                    .toUnit(API_TEMPERATURE_UNIT);
-                            if (quantity != null) {
-                                spTemp = quantity.toBigDecimal().setScale(1, RoundingMode.HALF_UP);
-                            }
-                        } else {
-                            spTemp = new BigDecimal(command.toString()).setScale(1, RoundingMode.HALF_UP);
-                        }
-                        if (spTemp != null) {
-                            pushSetpointUpdate(CHANNEL_SETPOINT_MODE_MANUAL, getSetpointEndTime(), spTemp.floatValue());
-                        }
-
-                        break;
-                    }
-                    case CHANNEL_PLANNING: {
-                        getApi().ifPresent(api -> {
-                            api.switchschedule(getParentId(), getId(), command.toString());
-                            updateState(channelUID, new StringType(command.toString()));
-                            invalidateParentCacheAndRefresh();
-                        });
-                    }
-                }
-            } catch (Exception e) {
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
-            }
-        }
-    }
-
-    private void pushSetpointUpdate(String target_mode, @Nullable Integer setpointEndtime,
-            @Nullable Float setpointTemp) {
-        getApi().ifPresent(api -> {
-            api.setthermpoint(getParentId(), getId(), target_mode, setpointEndtime, setpointTemp);
-            invalidateParentCacheAndRefresh();
-        });
-    }
-
-    private int getSetpointEndTime() {
-        Calendar cal = Calendar.getInstance();
-        cal.add(Calendar.MINUTE, getSetPointDefaultDuration());
-        cal.set(Calendar.SECOND, 0);
-        cal.set(Calendar.MILLISECOND, 0);
-        return (int) (cal.getTimeInMillis() / 1000);
-    }
-
-    private int getSetPointDefaultDuration() {
-        // TODO : this informations could be sourced from Netatmo API instead of a local configuration element
-        Configuration conf = config;
-        Object defaultDuration = conf != null ? conf.get(SETPOINT_DEFAULT_DURATION) : null;
-        if (defaultDuration instanceof BigDecimal) {
-            return ((BigDecimal) defaultDuration).intValue();
-        }
-        return 60;
-    }
-
-    private Optional<ThermostatApi> getApi() {
-        return getBridgeHandler().flatMap(handler -> handler.getThermostatApi());
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/ChannelTypeUtils.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/ChannelTypeUtils.java
new file mode 100644 (file)
index 0000000..10f6699
--- /dev/null
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2010-2022 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.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Measure;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.core.io.net.http.HttpUtil;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.RawType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * This class holds various channel values conversion methods
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ChannelTypeUtils {
+
+    public static @Nullable QuantityType<?> commandToQuantity(Command command, MeasureClass measureClass) {
+        Measure measureDef = measureClass.measureDefinition;
+        if (command instanceof QuantityType<?>) {
+            return ((QuantityType<?>) command).toUnit(measureDef.unit);
+        }
+        try {
+            double value = Double.parseDouble(command.toString());
+            return QuantityType.valueOf(value, measureDef.unit);
+        } catch (NumberFormatException ignore) {
+        }
+        return null;
+    }
+
+    public static State toStringType(String pattern, Object... parms) {
+        return toStringType(String.format(pattern, parms));
+    }
+
+    public static State toStringType(@Nullable Enum<?> value) {
+        return (value == null) ? UnDefType.NULL : new StringType(value.name());
+    }
+
+    public static State toStringType(@Nullable String value) {
+        return (value == null) ? UnDefType.NULL : new StringType(value);
+    }
+
+    public static State toDateTimeType(@Nullable ZonedDateTime zonedDateTime) {
+        return (zonedDateTime == null) ? UnDefType.NULL : new DateTimeType(zonedDateTime);
+    }
+
+    public static State toDateTimeType(Optional<ZonedDateTime> zonedDateTime) {
+        return zonedDateTime.map(zdt -> (State) new DateTimeType(zdt)).orElse(UnDefType.NULL);
+    }
+
+    public static State toQuantityType(@Nullable Double value, @Nullable MeasureClass measureClass) {
+        if (value != null && !value.isNaN()) {
+            if (measureClass != null) {
+                Measure measureDef = measureClass.measureDefinition;
+                BigDecimal measure = new BigDecimal(Math.min(measureDef.maxValue, Math.max(measureDef.minValue, value)))
+                        .setScale(measureDef.scale, RoundingMode.HALF_UP);
+                return new QuantityType<>(measure, measureDef.unit);
+            } else {
+                return new DecimalType(value);
+            }
+        }
+        return UnDefType.NULL;
+    }
+
+    public static State toQuantityType(@Nullable Number value, Unit<?> unit) {
+        return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
+    }
+
+    public static State toRawType(@Nullable String pictureUrl) {
+        if (pictureUrl != null) {
+            RawType picture = HttpUtil.downloadImage(pictureUrl);
+            if (picture != null) {
+                return picture;
+            }
+        }
+        return UnDefType.UNDEF;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/WeatherUtils.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/WeatherUtils.java
new file mode 100644 (file)
index 0000000..8240eb0
--- /dev/null
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2010-2022 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.utils;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This class holds various unit/measurement conversion methods
+ *
+ * @author Gaël L'hopital - Initial contribution
+ * @author Rob Nielsen - updated heat index
+ */
+@NonNullByDefault
+public class WeatherUtils {
+
+    /**
+     * Calculate the heat index using temperature and humidity
+     * https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
+     *
+     * @param temperature in (°C)
+     * @param humidity relative level (%)
+     * @return heatIndex in (°C)
+     */
+    public static double heatIndex(double temperature, double humidity) {
+        double tempF = (temperature * 9.0 / 5.0) + 32.0; // calculations are done in Fahrenheit
+        double heatIndex;
+        if (tempF >= 80.0) {
+            heatIndex = -42.379 + (2.04901523 * tempF) + (10.14333127 * humidity) - (0.22475541 * tempF * humidity)
+                    - (0.00683783 * tempF * tempF) - (0.05481717 * humidity * humidity)
+                    + (0.00122874 * tempF * tempF * humidity) + (0.00085282 * tempF * humidity * humidity)
+                    - (0.00000199 * tempF * tempF * humidity * humidity);
+            if (humidity < 13.0 && tempF <= 112.0) {
+                heatIndex -= ((13.0 - humidity) / 4.0) * Math.sqrt((17.0 - Math.abs(tempF - 95.0)) / 17.0);
+            } else if (humidity > 85.0 && tempF <= 87.0) {
+                heatIndex += ((humidity - 85.0) / 10.0) * ((87.0 - tempF) / 5.0);
+            }
+        } else {
+            heatIndex = 0.5 * (tempF + 61.0 + ((tempF - 68.0) * 1.2) + (humidity * 0.094));
+        }
+
+        return (heatIndex - 32) * 5.0 / 9.0; // convert back to Celsius
+    }
+
+    public static double dewPointDep(double temperature, double dewpoint) {
+        return temperature - dewpoint;
+    }
+
+    /**
+     * Compute the Dewpoint temperature given temperature and hygrometry
+     * valid up to 60 degrees, from
+     * http://en.wikipedia.org/wiki/Dew_point#Calculating_the_dew_point
+     *
+     * @param temperature in (°C)
+     * @param humidity relative level (%)
+     * @return dewpoint temperature
+     */
+    public static double dewPoint(double temperature, double humidity) {
+        double a = 17.271, b = 237.2;
+        double gamma = ((a * temperature) / (b + temperature)) + Math.log(humidity / 100.0);
+        return b * gamma / (a - gamma);
+    }
+
+    /**
+     * Compute the Humidex index given temperature and hygrometry
+     *
+     * @param temperature in (°C)
+     * @param hygro relative level (%)
+     * @return Humidex index value
+     */
+    public static double humidex(double temperature, double hygro) {
+        double result = 6.112 * Math.pow(10, 7.5 * temperature / (237.7 + temperature)) * hygro / 100;
+        return temperature + 0.555555556 * (result - 10);
+    }
+
+    /**
+     * Compute the associated scale appreciation of a given humidex index
+     * https://www.researchgate.net/figure/The-scale-of-Humidex-and-the-degree-of-comfort_tbl1_335293174
+     *
+     * @param Humidex index value
+     * @return scale between 0 and 4
+     */
+    public static int humidexScale(double humidex) {
+        return humidex < 30 ? 0 : humidex < 40 ? 1 : humidex < 45 ? 2 : humidex < 55 ? 3 : 4;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEvent.java
deleted file mode 100644 (file)
index b9456c3..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.webhook;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import com.google.gson.annotations.SerializedName;
-
-/**
- * The {@link NAWebhookCameraEvent} is responsible to hold
- * data given back by the Netatmo API when calling the webhook
- *
- * @author Gaël L'hopital - Initial contribution
- *
- */
-public class NAWebhookCameraEvent {
-
-    public enum AppTypeEnum {
-        @SerializedName("app_camera")
-        CAMERA("camera");
-
-        private String value;
-
-        AppTypeEnum(String value) {
-            this.value = value;
-        }
-
-        @Override
-        public String toString() {
-            return value;
-        }
-    }
-
-    @SerializedName("app_type")
-    private AppTypeEnum appType = null;
-
-    public AppTypeEnum getAppType() {
-        return appType;
-    }
-
-    public enum EventTypeEnum {
-        @SerializedName("person")
-        PERSON("person"),
-
-        @SerializedName("person_away")
-        PERSON_AWAY("person_away"),
-
-        @SerializedName("movement")
-        MOVEMENT("movement"),
-
-        @SerializedName("outdoor")
-        OUTDOOR("outdoor"),
-
-        @SerializedName("connection")
-        CONNECTION("connection"),
-
-        @SerializedName("disconnection")
-        DISCONNECTION("disconnection"),
-
-        @SerializedName("on")
-        ON("on"),
-
-        @SerializedName("off")
-        OFF("off"),
-
-        @SerializedName("boot")
-        BOOT("boot"),
-
-        @SerializedName("sd")
-        SD("sd"),
-
-        @SerializedName("alim")
-        ALIM("alim"),
-
-        @SerializedName("daily_summary")
-        DAILY_SUMMARY("daily_summary"),
-
-        @SerializedName("new_module")
-        NEW_MODULE("new_module"),
-
-        @SerializedName("module_connect")
-        MODULE_CONNECT("module_connect"),
-
-        @SerializedName("module_disconnect")
-        MODULE_DISCONNECT("module_disconnect"),
-
-        @SerializedName("module_low_battery")
-        MODULE_LOW_BATTERY("module_low_battery"),
-
-        @SerializedName("module_end_update")
-        MODULE_END_UPDATE("module_end_update"),
-
-        @SerializedName("tag_big_move")
-        TAG_BIG_MOVE("tag_big_move"),
-
-        @SerializedName("tag_small_move")
-        TAG_SMALL_MOVE("tag_small_move"),
-
-        @SerializedName("tag_uninstalled")
-        TAG_UNINSTALLED("tag_uninstalled"),
-
-        @SerializedName("tag_open")
-        TAG_OPEN("tag_open");
-
-        private String value;
-
-        EventTypeEnum(String value) {
-            this.value = value;
-        }
-
-        @Override
-        public String toString() {
-            return value;
-        }
-    }
-
-    @SerializedName("event_type")
-    private EventTypeEnum eventType = null;
-
-    public EventTypeEnum getEventType() {
-        return eventType;
-    }
-
-    @SerializedName("camera_id")
-    String cameraId;
-
-    public String getCameraId() {
-        return cameraId;
-    }
-
-    @SerializedName("home_id")
-    String homeId;
-
-    public String getHomeId() {
-        return homeId;
-    }
-
-    @SerializedName("persons")
-    private List<NAWebhookCameraEventPerson> persons = new ArrayList<>();
-
-    public List<NAWebhookCameraEventPerson> getPersons() {
-        return persons;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEventPerson.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEventPerson.java
deleted file mode 100644 (file)
index 39d1ebe..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.webhook;
-
-import com.google.gson.annotations.SerializedName;
-
-/**
- * The {@link NAWebhookCameraEventPerson} is responsible to hold
- * data given back by the Netatmo API when calling the webhook
- *
- * @author Gaël L'hopital - Initial contribution
- *
- */
-public class NAWebhookCameraEventPerson {
-    @SerializedName("id")
-    String id;
-
-    public String getId() {
-        return id;
-    }
-
-    @SerializedName("face_id")
-    String faceId;
-
-    public String getFaceId() {
-        return faceId;
-    }
-
-    @SerializedName("face_key")
-    String faceKey;
-
-    public String getFaceKey() {
-        return faceKey;
-    }
-
-    @SerializedName("is_known")
-    Boolean isKnown;
-
-    public Boolean isKnown() {
-        return isKnown;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NetatmoServlet.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NetatmoServlet.java
new file mode 100644 (file)
index 0000000..ffd091a
--- /dev/null
@@ -0,0 +1,163 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.webhook;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Scanner;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MediaType;
+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.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.SecurityApi;
+import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent;
+import org.openhab.binding.netatmo.internal.deserialization.NADeserializer;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * HTTP servlet for Netatmo Webhook.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NetatmoServlet extends HttpServlet {
+    private static final long serialVersionUID = -354583910860541214L;
+    private static final String CALLBACK_URI = "/" + BINDING_ID;
+
+    private final Logger logger = LoggerFactory.getLogger(NetatmoServlet.class);
+    private final Map<String, EventCapability> dataListeners = new ConcurrentHashMap<>();
+    private final HttpService httpService;
+    private final NADeserializer deserializer;
+    private final Optional<SecurityApi> securityApi;
+    private boolean hookSet = false;
+
+    public NetatmoServlet(HttpService httpService, ApiBridgeHandler apiBridge, String webHookUrl) {
+        this.httpService = httpService;
+        this.deserializer = apiBridge.getDeserializer();
+        this.securityApi = Optional.ofNullable(apiBridge.getRestManager(SecurityApi.class));
+        securityApi.ifPresent(api -> {
+            try {
+                httpService.registerServlet(CALLBACK_URI, this, null, httpService.createDefaultHttpContext());
+                logger.debug("Started Netatmo Webhook Servlet at '{}'", CALLBACK_URI);
+                URI uri = UriBuilder.fromUri(webHookUrl).path(BINDING_ID).build();
+                try {
+                    logger.info("Setting Netatmo Welcome WebHook to {}", uri.toString());
+                    api.addwebhook(uri);
+                    hookSet = true;
+                } catch (UriBuilderException e) {
+                    logger.info("webhookUrl is not a valid URI '{}' : {}", uri, e.getMessage());
+                } catch (NetatmoException e) {
+                    logger.info("Error setting webhook : {}", e.getMessage());
+                }
+            } catch (ServletException | NamespaceException e) {
+                logger.warn("Could not start Netatmo Webhook Servlet : {}", e.getMessage());
+            }
+        });
+    }
+
+    public void dispose() {
+        securityApi.ifPresent(api -> {
+            if (hookSet) {
+                logger.info("Releasing Netatmo Welcome WebHook");
+                try {
+                    api.dropWebhook();
+                } catch (NetatmoException e) {
+                    logger.warn("Error releasing webhook : {}", e.getMessage());
+                }
+            }
+            httpService.unregister(CALLBACK_URI);
+        });
+        logger.debug("Netatmo Webhook Servlet stopped");
+    }
+
+    @Override
+    protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
+        if (req != null && resp != null) {
+            String data = inputStreamToString(req.getInputStream());
+            if (!data.isEmpty()) {
+                logger.debug("Event transmitted from restService : {}", data);
+                try {
+                    WebhookEvent event = deserializer.deserialize(WebhookEvent.class, data);
+                    List<String> tobeNotified = collectNotified(event);
+                    dataListeners.keySet().stream().filter(tobeNotified::contains).forEach(id -> {
+                        EventCapability module = dataListeners.get(id);
+                        if (module != null) {
+                            module.setNewData(event);
+                        }
+                    });
+                } catch (NetatmoException e) {
+                    logger.info("Error deserializing webhook data received : {}. {}", data, e.getMessage());
+                }
+            }
+            resp.setCharacterEncoding(StandardCharsets.UTF_8.name());
+            resp.setContentType(MediaType.APPLICATION_JSON);
+            resp.setHeader("Access-Control-Allow-Origin", "*");
+            resp.setHeader("Access-Control-Allow-Methods", HttpMethod.POST);
+            resp.setIntHeader("Access-Control-Max-Age", 3600);
+            resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
+            resp.getWriter().write("");
+        }
+    }
+
+    private List<String> collectNotified(WebhookEvent event) {
+        List<String> result = new ArrayList<>();
+        result.add(event.getCameraId());
+        String person = event.getPersonId();
+        if (person != null) {
+            result.add(person);
+        }
+        result.addAll(event.getPersons().keySet());
+        return result.stream().distinct().collect(Collectors.toList());
+    }
+
+    public void registerDataListener(String id, EventCapability dataListener) {
+        dataListeners.put(id, dataListener);
+    }
+
+    public void unregisterDataListener(EventCapability dataListener) {
+        dataListeners.entrySet().removeIf(entry -> entry.getValue().equals(dataListener));
+    }
+
+    private String inputStreamToString(InputStream is) throws IOException {
+        String value = "";
+        try (Scanner scanner = new Scanner(is)) {
+            scanner.useDelimiter("\\A");
+            value = scanner.hasNext() ? scanner.next() : "";
+        }
+        return value;
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/WelcomeWebHookServlet.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/WelcomeWebHookServlet.java
deleted file mode 100644 (file)
index bd39534..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.webhook;
-
-import java.io.IOException;
-import java.util.Objects;
-import java.util.Scanner;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
-import org.osgi.service.http.HttpService;
-import org.osgi.service.http.NamespaceException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gson.Gson;
-
-/**
- * Main OSGi service and HTTP servlet for Netatmo Welcome Webhook.
- *
- * @author Gaël L'hopital - Initial contribution
- */
-@NonNullByDefault
-public class WelcomeWebHookServlet extends HttpServlet {
-    private static final long serialVersionUID = 1288539782077957954L;
-    private static final String PATH = "/netatmo/%s/camera";
-    private static final String APPLICATION_JSON = "application/json";
-    private static final String CHARSET = "utf-8";
-
-    private final Gson gson = new Gson();
-
-    private final Logger logger = LoggerFactory.getLogger(WelcomeWebHookServlet.class);
-
-    private HttpService httpService;
-    private @Nullable NetatmoBridgeHandler bridgeHandler;
-    private String path;
-
-    public WelcomeWebHookServlet(HttpService httpService, String id) {
-        this.httpService = httpService;
-        this.path = String.format(PATH, id);
-    }
-
-    /**
-     * OSGi activation callback.
-     *
-     * @param config Service config.
-     */
-    public void activate(NetatmoBridgeHandler bridgeHandler) {
-        this.bridgeHandler = bridgeHandler;
-        try {
-            httpService.registerServlet(path, this, null, httpService.createDefaultHttpContext());
-            logger.debug("Started Netatmo Webhook servlet at {}", path);
-        } catch (ServletException | NamespaceException e) {
-            logger.error("Could not start Netatmo Webhook servlet: {}", e.getMessage(), e);
-        }
-    }
-
-    /**
-     * OSGi deactivation callback.
-     */
-    public void deactivate() {
-        httpService.unregister(path);
-        logger.debug("Netatmo webhook servlet stopped");
-        this.bridgeHandler = null;
-    }
-
-    @Override
-    protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
-            throws ServletException, IOException {
-        if (req == null || resp == null) {
-            return;
-        }
-
-        String data = inputStreamToString(req);
-        NetatmoBridgeHandler handler = bridgeHandler;
-        if (!data.isEmpty() && handler != null) {
-            NAWebhookCameraEvent event = gson.fromJson(data, NAWebhookCameraEvent.class);
-            logger.debug("Event transmitted from restService");
-            handler.webHookEvent(Objects.requireNonNull(event));
-        }
-
-        setHeaders(resp);
-        resp.getWriter().write("");
-    }
-
-    private String inputStreamToString(HttpServletRequest req) throws IOException {
-        String value = "";
-        try (Scanner scanner = new Scanner(req.getInputStream())) {
-            scanner.useDelimiter("\\A");
-            value = scanner.hasNext() ? scanner.next() : "";
-        }
-        return value;
-    }
-
-    private void setHeaders(HttpServletResponse response) {
-        response.setCharacterEncoding(CHARSET);
-        response.setContentType(APPLICATION_JSON);
-        response.setHeader("Access-Control-Allow-Origin", "*");
-        response.setHeader("Access-Control-Allow-Methods", "POST");
-        response.setHeader("Access-Control-Max-Age", "3600");
-        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
-    }
-
-    public String getPath() {
-        return path;
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeCameraHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeCameraHandler.java
deleted file mode 100644 (file)
index 0a6f8d2..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.welcome;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.camera.CameraHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-/**
- * {@link NAWelcomeCameraHandler} is the class used to handle the Welcome Camera Data
- *
- * @author Ing. Peter Weiss - Initial contribution
- *
- */
-@NonNullByDefault
-public class NAWelcomeCameraHandler extends CameraHandler {
-
-    public NAWelcomeCameraHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        switch (channelId) {
-            case CHANNEL_WELCOME_CAMERA_STATUS:
-                return getStatusState();
-            case CHANNEL_WELCOME_CAMERA_SDSTATUS:
-                return getSdStatusState();
-            case CHANNEL_WELCOME_CAMERA_ALIMSTATUS:
-                return getAlimStatusState();
-            case CHANNEL_WELCOME_CAMERA_ISLOCAL:
-                return getIsLocalState();
-            case CHANNEL_WELCOME_CAMERA_LIVEPICTURE_URL:
-                return getLivePictureURLState();
-            case CHANNEL_WELCOME_CAMERA_LIVEPICTURE:
-                return getLivePictureState();
-            case CHANNEL_WELCOME_CAMERA_LIVESTREAM_URL:
-                return getLiveStreamState();
-        }
-        return super.getNAThingProperty(channelId);
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandler.java
deleted file mode 100644 (file)
index 6363bab..0000000
+++ /dev/null
@@ -1,270 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.welcome;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Calendar;
-import java.util.Comparator;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.function.Function;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
-import org.openhab.binding.netatmo.internal.camera.CameraHandler;
-import org.openhab.binding.netatmo.internal.handler.AbstractNetatmoThingHandler;
-import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
-import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.swagger.client.model.NAWelcomeEvent;
-import io.swagger.client.model.NAWelcomeHome;
-import io.swagger.client.model.NAWelcomePlace;
-import io.swagger.client.model.NAWelcomeSnapshot;
-import io.swagger.client.model.NAWelcomeSubEvent;
-
-/**
- * {@link NAWelcomeHomeHandler} is the class used to handle the Welcome Home Data
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Ing. Peter Weiss - Welcome camera implementation
- *
- */
-@NonNullByDefault
-public class NAWelcomeHomeHandler extends NetatmoDeviceHandler<NAWelcomeHome> {
-    private final Logger logger = LoggerFactory.getLogger(NAWelcomeHomeHandler.class);
-
-    private int iPersons = -1;
-    private int iUnknowns = -1;
-    private @Nullable NAWelcomeEvent lastEvent;
-    private boolean isNewLastEvent;
-    private @Nullable Integer dataTimeStamp;
-
-    public NAWelcomeHomeHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    protected Optional<NAWelcomeHome> updateReadings() {
-        Optional<NAWelcomeHome> result = getBridgeHandler().flatMap(handler -> handler.getWelcomeDataBody(getId()))
-                .map(dataBody -> nonNullStream(dataBody.getHomes())
-                        .filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
-        // data time stamp is updated to now as WelcomeDataBody does not provide any information according to this need
-        dataTimeStamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
-        result.ifPresent(home -> {
-            nonNullList(home.getCameras()).forEach(camera -> childs.put(camera.getId(), camera));
-
-            // Check how many persons are at home
-            iPersons = 0;
-            iUnknowns = 0;
-
-            logger.debug("welcome home '{}' calculate Persons at home count", getId());
-            nonNullList(home.getPersons()).forEach(person -> {
-                iPersons += person.isOutOfSight() ? 0 : 1;
-                if (person.getPseudo() != null) {
-                    childs.put(person.getId(), person);
-                } else {
-                    iUnknowns += person.isOutOfSight() ? 0 : 1;
-                }
-            });
-
-            NAWelcomeEvent previousLastEvent = lastEvent;
-            lastEvent = nonNullStream(home.getEvents()).max(Comparator.comparingInt(NAWelcomeEvent::getTime))
-                    .orElse(null);
-            isNewLastEvent = previousLastEvent != null && !previousLastEvent.equals(lastEvent);
-        });
-        return result;
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        Optional<NAWelcomeEvent> lastEvt = getLastEvent();
-        switch (channelId) {
-            case CHANNEL_WELCOME_HOME_CITY:
-                return getPlaceInfo(NAWelcomePlace::getCity);
-            case CHANNEL_WELCOME_HOME_COUNTRY:
-                return getPlaceInfo(NAWelcomePlace::getCountry);
-            case CHANNEL_WELCOME_HOME_TIMEZONE:
-                return getPlaceInfo(NAWelcomePlace::getTimezone);
-            case CHANNEL_WELCOME_HOME_PERSONCOUNT:
-                return iPersons != -1 ? new DecimalType(iPersons) : UnDefType.UNDEF;
-            case CHANNEL_WELCOME_HOME_UNKNOWNCOUNT:
-                return iUnknowns != -1 ? new DecimalType(iUnknowns) : UnDefType.UNDEF;
-            case CHANNEL_WELCOME_EVENT_TYPE:
-                return lastEvt.map(e -> toStringType(e.getType())).orElse(UnDefType.UNDEF);
-            case CHANNEL_WELCOME_EVENT_TIME:
-                return lastEvt.map(e -> toDateTimeType(e.getTime(), timeZoneProvider.getTimeZone()))
-                        .orElse(UnDefType.UNDEF);
-            case CHANNEL_WELCOME_EVENT_CAMERAID:
-                if (lastEvt.isPresent()) {
-                    return findNAThing(lastEvt.get().getCameraId()).map(c -> toStringType(c.getThing().getLabel()))
-                            .orElse(UnDefType.UNDEF);
-                } else {
-                    return UnDefType.UNDEF;
-                }
-            case CHANNEL_WELCOME_EVENT_PERSONID:
-                if (lastEvt.isPresent()) {
-                    return findNAThing(lastEvt.get().getPersonId()).map(p -> toStringType(p.getThing().getLabel()))
-                            .orElse(UnDefType.UNDEF);
-                } else {
-                    return UnDefType.UNDEF;
-                }
-            case CHANNEL_WELCOME_EVENT_SNAPSHOT:
-                return findSnapshotURL().map(url -> toRawType(url)).orElse(UnDefType.UNDEF);
-            case CHANNEL_WELCOME_EVENT_SNAPSHOT_URL:
-                return findSnapshotURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
-            case CHANNEL_WELCOME_EVENT_VIDEO_URL:
-                if (lastEvt.isPresent() && lastEvt.get().getVideoId() != null) {
-                    String cameraId = lastEvt.get().getCameraId();
-                    Optional<AbstractNetatmoThingHandler> thing = findNAThing(cameraId);
-                    if (thing.isPresent()) {
-                        CameraHandler eventCamera = (CameraHandler) thing.get();
-                        Optional<String> streamUrl = eventCamera.getStreamURL(lastEvt.get().getVideoId());
-                        if (streamUrl.isPresent()) {
-                            return new StringType(streamUrl.get());
-                        }
-                    }
-                }
-                return UnDefType.UNDEF;
-            case CHANNEL_WELCOME_EVENT_VIDEOSTATUS:
-                return lastEvt.map(e -> e.getVideoId() != null ? toStringType(e.getVideoStatus()) : UnDefType.UNDEF)
-                        .orElse(UnDefType.UNDEF);
-            case CHANNEL_WELCOME_EVENT_ISARRIVAL:
-                return lastEvt.map(e -> toOnOffType(e.isIsArrival())).orElse(UnDefType.UNDEF);
-            case CHANNEL_WELCOME_EVENT_MESSAGE:
-                return findEventMessage().map(m -> (State) new StringType(m.replace("<b>", "").replace("</b>", "")))
-                        .orElse(UnDefType.UNDEF);
-            case CHANNEL_WELCOME_EVENT_SUBTYPE:
-                return lastEvt.map(e -> toDecimalType(e.getSubType())).orElse(UnDefType.UNDEF);
-        }
-        return super.getNAThingProperty(channelId);
-    }
-
-    @Override
-    protected void triggerChannelIfRequired(String channelId) {
-        if (isNewLastEvent) {
-            if (CHANNEL_CAMERA_EVENT.equals(channelId)) {
-                findDetectedObjectTypes(getLastEvent())
-                        .forEach(detectedType -> triggerChannel(channelId, detectedType));
-            }
-        }
-        super.triggerChannelIfRequired(channelId);
-    }
-
-    private static Set<String> findDetectedObjectTypes(Optional<NAWelcomeEvent> eventOptional) {
-        Set<String> detectedObjectTypes = new TreeSet<>();
-        if (!eventOptional.isPresent()) {
-            return detectedObjectTypes;
-        }
-
-        NAWelcomeEvent event = eventOptional.get();
-
-        if (NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.toString().equals(event.getType())) {
-            if (event.getPersonId() != null) {
-                detectedObjectTypes.add(NAWelcomeSubEvent.TypeEnum.HUMAN.name());
-            } else {
-                Optional<NAWelcomeSubEvent.TypeEnum> detectedCategory = findDetectedCategory(event);
-                if (detectedCategory.isPresent()) {
-                    detectedObjectTypes.add(detectedCategory.get().name());
-                } else {
-                    detectedObjectTypes.add(NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.name());
-                }
-            }
-        }
-
-        nonNullList(event.getEventList()).forEach(subEvent -> {
-            String detectedObjectType = subEvent.getType().name();
-            detectedObjectTypes.add(detectedObjectType);
-        });
-        return detectedObjectTypes;
-    }
-
-    private static Optional<NAWelcomeSubEvent.TypeEnum> findDetectedCategory(NAWelcomeEvent event) {
-        NAWelcomeEvent.CategoryEnum category = event.getCategory();
-        if (category != null) {
-            // It is safe to convert the enum, both enums have the same values.
-            return Optional.of(NAWelcomeSubEvent.TypeEnum.valueOf(category.name()));
-        }
-        return Optional.empty();
-    }
-
-    private Optional<String> findEventMessage() {
-        Optional<NAWelcomeEvent> lastEvt = getLastEvent();
-        if (lastEvt.isPresent()) {
-            @Nullable
-            String message = lastEvt.get().getMessage();
-            if (message != null) {
-                return Optional.of(message);
-            }
-
-            return lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getMessage);
-        }
-        return Optional.empty();
-    }
-
-    /**
-     * Returns the Url of the picture
-     *
-     * @return Url of the picture or null
-     */
-    protected Optional<String> findSnapshotURL() {
-        Optional<NAWelcomeEvent> lastEvt = getLastEvent();
-        if (lastEvt.isPresent()) {
-            @Nullable
-            NAWelcomeSnapshot snapshot = lastEvt.get().getSnapshot();
-            if (snapshot == null) {
-                snapshot = lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getSnapshot).orElse(null);
-            }
-
-            if (snapshot != null && snapshot.getId() != null && snapshot.getKey() != null) {
-                return Optional.of(WELCOME_PICTURE_URL + "?" + WELCOME_PICTURE_IMAGEID + "=" + snapshot.getId() + "&"
-                        + WELCOME_PICTURE_KEY + "=" + snapshot.getKey());
-            } else {
-                logger.debug("Unable to build snapshot url for Home : {}", getId());
-            }
-        }
-        return Optional.empty();
-    }
-
-    @Override
-    protected Optional<Integer> getDataTimestamp() {
-        Integer timestamp = dataTimeStamp;
-        return timestamp != null ? Optional.of(timestamp) : Optional.empty();
-    }
-
-    private State getPlaceInfo(Function<NAWelcomePlace, String> infoGetFunction) {
-        return getDevice().map(d -> toStringType(infoGetFunction.apply(d.getPlace()))).orElse(UnDefType.UNDEF);
-    }
-
-    private Optional<NAWelcomeSubEvent> findFirstSubEvent(NAWelcomeEvent event) {
-        return Optional.ofNullable(event).map(NAWelcomeEvent::getEventList)
-                .flatMap(subEvents -> nonNullStream(subEvents).findFirst());
-    }
-
-    private Optional<NAWelcomeEvent> getLastEvent() {
-        NAWelcomeEvent evt = lastEvent;
-        return evt != null ? Optional.of(evt) : Optional.empty();
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomePersonHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomePersonHandler.java
deleted file mode 100644 (file)
index c0a61e3..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.welcome;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-import io.swagger.client.api.WelcomeApi;
-import io.swagger.client.model.NAWelcomeEvent;
-import io.swagger.client.model.NAWelcomeEventResponse;
-import io.swagger.client.model.NAWelcomeFace;
-import io.swagger.client.model.NAWelcomePerson;
-
-/**
- * {@link NAWelcomePersonHandler} is the class used to handle the Welcome Home Data
- *
- * @author Ing. Peter Weiss - Initial contribution
- *
- */
-@NonNullByDefault
-public class NAWelcomePersonHandler extends NetatmoModuleHandler<NAWelcomePerson> {
-    private @Nullable String avatarURL;
-    private @Nullable NAWelcomeEvent lastEvent;
-
-    public NAWelcomePersonHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
-        super(thing, timeZoneProvider);
-    }
-
-    @Override
-    public void updateChannels(Object module) {
-        if (isRefreshRequired()) {
-            getApi().ifPresent(api -> {
-                NAWelcomeEventResponse eventResponse = api.getlasteventof(getParentId(), getId(), 10);
-
-                // Search the last event for this person
-                nonNullList(eventResponse.getBody().getEventsList()).forEach(event -> {
-                    if (event.getPersonId() != null && event.getPersonId().equalsIgnoreCase(getId())
-                            && (lastEvent == null || lastEvent.getTime() < event.getTime())) {
-                        lastEvent = event;
-                    }
-                });
-            });
-
-            setRefreshRequired(false);
-        }
-        super.updateChannels(module);
-    }
-
-    @Override
-    protected State getNAThingProperty(String channelId) {
-        Optional<NAWelcomeEvent> lastEvt = getLastEvent();
-        String url;
-        switch (channelId) {
-            case CHANNEL_WELCOME_PERSON_LASTSEEN:
-                return getModule().map(m -> toDateTimeType(m.getLastSeen(), timeZoneProvider.getTimeZone()))
-                        .orElse(UnDefType.UNDEF);
-            case CHANNEL_WELCOME_PERSON_ATHOME:
-                return getModule().map(m -> m.isOutOfSight() != null ? toOnOffType(!m.isOutOfSight()) : UnDefType.UNDEF)
-                        .orElse(UnDefType.UNDEF);
-            case CHANNEL_WELCOME_PERSON_AVATAR_URL:
-                return toStringType(getAvatarURL());
-            case CHANNEL_WELCOME_PERSON_AVATAR:
-                url = getAvatarURL();
-                return url != null ? toRawType(url) : UnDefType.UNDEF;
-            case CHANNEL_WELCOME_PERSON_LASTMESSAGE:
-                return (lastEvt.isPresent() && lastEvt.get().getMessage() != null)
-                        ? toStringType(lastEvt.get().getMessage().replace("<b>", "").replace("</b>", ""))
-                        : UnDefType.UNDEF;
-            case CHANNEL_WELCOME_PERSON_LASTTIME:
-                return lastEvt.isPresent() ? toDateTimeType(lastEvt.get().getTime(), timeZoneProvider.getTimeZone())
-                        : UnDefType.UNDEF;
-            case CHANNEL_WELCOME_PERSON_LASTEVENT:
-                url = getLastEventURL();
-                return url != null ? toRawType(url) : UnDefType.UNDEF;
-            case CHANNEL_WELCOME_PERSON_LASTEVENT_URL:
-                return getLastEventURL() != null ? toStringType(getLastEventURL()) : UnDefType.UNDEF;
-        }
-        return super.getNAThingProperty(channelId);
-    }
-
-    private @Nullable String getLastEventURL() {
-        Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
-        Optional<NAWelcomeEvent> lastEvt = getLastEvent();
-        if (handler.isPresent() && lastEvt.isPresent() && lastEvt.get().getSnapshot() != null) {
-            return handler.get().getPictureUrl(lastEvt.get().getSnapshot().getId(),
-                    lastEvt.get().getSnapshot().getKey());
-        }
-        return null;
-    }
-
-    private @Nullable String getAvatarURL() {
-        Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
-        Optional<NAWelcomePerson> person = getModule();
-        if (handler.isPresent() && avatarURL == null && person.isPresent()) {
-            NAWelcomeFace face = person.get().getFace();
-            if (face != null) {
-                avatarURL = handler.get().getPictureUrl(face.getId(), face.getKey());
-            }
-        }
-        return avatarURL;
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-        super.handleCommand(channelUID, command);
-        if ((command instanceof OnOffType) && (CHANNEL_WELCOME_PERSON_ATHOME.equalsIgnoreCase(channelUID.getId()))) {
-            getApi().ifPresent(api -> {
-                if ((OnOffType) command == OnOffType.OFF) {
-                    api.setpersonsaway(getParentId(), getId());
-                } else {
-                    api.setpersonshome(getParentId(), "[\"" + getId() + "\"]");
-                }
-                invalidateParentCacheAndRefresh();
-            });
-        }
-    }
-
-    private Optional<WelcomeApi> getApi() {
-        return getBridgeHandler().flatMap(handler -> handler.getWelcomeApi());
-    }
-
-    private Optional<NAWelcomeEvent> getLastEvent() {
-        NAWelcomeEvent evt = lastEvent;
-        return evt != null ? Optional.of(evt) : Optional.empty();
-    }
-}
index a7c08125c6472b4462ee1c490287805b89b68754..976fa7a65e82aa801e4fe2a00e7023ec091c5634 100644 (file)
@@ -7,11 +7,24 @@
        <description>The Netatmo binding integrates Weather Station with companion modules, Healthy Home Coach, Thermostat Plug
                and Welcome Camera.</description>
 
+
        <config-description>
-               <parameter name="backgroundDiscovery" type="boolean" required="false">
-                       <label>Background Discovery</label>
-                       <description>If set to true, the device and its associated modules are updated in the discovery inbox at each API
-                               call run to refresh device data. Default is false.</description>
+               <parameter name="features" type="text" required="true" multiple="true">
+                       <label>Feature Area</label>
+                       <description>Defines the set of capabilities you want to operate on.</description>
+                       <options>
+                               <option value="AIR_CARE">Air Care</option>
+                               <option value="WEATHER">Weather</option>
+                               <option value="ENERGY">Energy</option>
+                               <option value="SECURITY">Security</option>
+                       </options>
+                       <limitToOptions>true</limitToOptions>
+                       <default>WEATHER</default>
+               </parameter>
+
+               <parameter name="readFriends" type="boolean">
+                       <label>Access Guests</label>
+                       <description>For Weather Stations: A friend gave you access to their Netatmo Weather Station.</description>
                        <default>false</default>
                </parameter>
        </config-description>
index 568e00deb1bf282b9fc2943b4faca31f03524b94..e7023f6ce00d6732f6d5622305ac38e6b4c27a83 100644 (file)
@@ -5,7 +5,7 @@
        xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
                https://openhab.org/schemas/config-description-1.0.0.xsd">
 
-       <config-description uri="thing-type:netatmo:bridge">
+       <config-description uri="netatmo:api_bridge">
                <parameter name="clientId" type="text" required="true">
                        <label>Client ID</label>
                        <description>Client ID provided for the application you created on http://dev.netatmo.com/createapp</description>
 
                <parameter name="clientSecret" type="text" required="true">
                        <label>Client Secret</label>
-                       <description>Client Secret provided for the application you created</description>
+                       <description>Client Secret provided for the application you created.</description>
                        <context>password</context>
                </parameter>
 
                <parameter name="username" type="text" required="true">
                        <label>Username</label>
-                       <description>Your Netatmo API username (email)</description>
+                       <description>Your Netatmo API username (email).</description>
                </parameter>
 
                <parameter name="password" type="text" required="true">
                        <label>Password</label>
-                       <description>Your Netatmo API password</description>
+                       <description>Your Netatmo API password.</description>
                        <context>password</context>
                </parameter>
 
-               <parameter name="readStation" type="boolean">
-                       <label>Access Weather Station</label>
-                       <description>Read weather station's data.</description>
-                       <default>true</default>
-               </parameter>
-
-               <parameter name="readHealthyHomeCoach" type="boolean">
-                       <label>Access Healthy Home Coach</label>
-                       <description>Read healthy home coach's data.</description>
-                       <default>false</default>
-               </parameter>
-
-               <parameter name="readThermostat" type="boolean">
-                       <label>Access Thermostat</label>
-                       <description>Read and Write thermostat's data.</description>
-                       <default>false</default>
-               </parameter>
-
-               <parameter name="readWelcome" type="boolean">
-                       <label>Access Welcome Camera</label>
-                       <description>Read and Access Welcome camera's data.</description>
-                       <default>false</default>
-               </parameter>
-
-               <parameter name="readPresence" type="boolean">
-                       <label>Access Presence Camera</label>
-                       <description>Read and Access Presence camera's data.</description>
-                       <default>false</default>
-               </parameter>
-
-               <parameter name="webHookUrl" type="text">
+               <parameter name="webHookUrl" type="text" required="false">
                        <label>Webhook Address</label>
-                       <description>Protocol, public IP and port to access OH2 server from Internet.</description>
-                       <advanced>true</advanced>
+                       <description>Protocol, public IP and port to access openHAB server from Internet.</description>
                </parameter>
 
                <parameter name="reconnectInterval" type="integer" unit="s">
                        <label>Reconnect Interval</label>
                        <description>The reconnection interval to Netatmo API (in s).</description>
-                       <default>5400</default>
-                       <advanced>true</advanced>
+                       <default>300</default>
                </parameter>
-
        </config-description>
 
-       <config-description uri="thing-type:netatmo:station">
-               <parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
-                       <label>Equipment ID</label>
-                       <description>Id of the Device (mac address)</description>
+       <config-description uri="netatmo:sum_rain:config">
+               <parameter name="period" type="text">
+                       <label>Period</label>
+                       <description>Observation period for summing rain quantities.</description>
+                       <options>
+                               <option value="30min">30 minutes</option>
+                               <option value="1hour">1 hour</option>
+                               <option value="3hours">3 hours</option>
+                               <option value="1day">This day</option>
+                               <option value="1week">This week</option>
+                               <option value="1month">This month</option>
+                       </options>
+                       <default>1week</default>
                </parameter>
        </config-description>
 
-       <config-description uri="thing-type:netatmo:plug">
-               <parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
-                       <label>Equipment ID</label>
-                       <description>Id of the Device (mac address)</description>
+       <config-description uri="netatmo:measure:config">
+               <parameter name="limit" type="text">
+                       <label>Boundary</label>
+                       <description>Defines the requested boundary.</description>
+                       <options>
+                               <option value="MIN">Minimum</option>
+                               <option value="MAX">Maximum</option>
+                       </options>
+                       <default>MIN</default>
+               </parameter>
+               <parameter name="period" type="text">
+                       <label>Period</label>
+                       <description>Observation period for searched boundary.</description>
+                       <options>
+                               <option value="30min">30 minutes</option>
+                               <option value="1hour">1 hour</option>
+                               <option value="3hours">3 hours</option>
+                               <option value="1day">This day</option>
+                               <option value="1week">This week</option>
+                               <option value="1month">This month</option>
+                       </options>
+                       <default>1week</default>
                </parameter>
        </config-description>
 
-       <config-description uri="thing-type:netatmo:module">
-               <parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
-                       <label>Module ID</label>
-                       <description>Id of the Module</description>
-               </parameter>
-
-               <parameter name="parentId" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
-                       <label>Device ID</label>
-                       <description>Id of the main device</description>
+       <config-description uri="netatmo:timestamp:config">
+               <parameter name="limit" type="text">
+                       <label>Boundary</label>
+                       <description>Defines the requested boundary.</description>
+                       <options>
+                               <option value="DATE_MIN">Timestamp of minimum</option>
+                               <option value="DATE_MAX">Timestamp of maximum</option>
+                       </options>
+                       <default>DATE_MIN</default>
+               </parameter>
+               <parameter name="period" type="text">
+                       <label>Period</label>
+                       <description>Observation period for the queried value.</description>
+                       <options>
+                               <option value="1week">This week</option>
+                               <option value="1month">This month</option>
+                       </options>
+                       <default>1week</default>
                </parameter>
        </config-description>
 
-       <config-description uri="thing-type:netatmo:natherm1">
+       <config-description uri="netatmo:device">
                <parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
-                       <label>Module ID</label>
-                       <description>Id of the Module</description>
-               </parameter>
-
-               <parameter name="parentId" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
-                       <label>Device ID</label>
-                       <description>Id of the main device</description>
+                       <label>Equipment ID</label>
+                       <description>ID of the device (MAC address).</description>
                </parameter>
+       </config-description>
 
-               <parameter name="setpointDefaultDuration" type="integer">
-                       <label>Setpoint Duration</label>
-                       <description>Default duration of thermostat change when force to max or manual.</description>
-                       <default>60</default>
-                       <advanced>true</advanced>
+       <config-description uri="netatmo:virtual">
+               <parameter name="id" type="text" required="true">
+                       <label>Thing ID</label>
+                       <description>Unique identifier of the thing defined by Netatmo.</description>
                </parameter>
        </config-description>
 
-       <config-description uri="thing-type:netatmo:welcomehome">
+       <config-description uri="netatmo:home">
                <parameter name="id" type="text" required="true">
-                       <label>Home ID</label>
-                       <description>UUID of the home</description>
+                       <label>Thing ID</label>
+                       <description>Unique identifier of the thing defined by Netatmo.</description>
                </parameter>
 
-               <parameter name="refreshInterval" type="integer" min="2000" unit="ms">
+               <parameter name="refreshInterval" type="integer" min="20" unit="s">
                        <label>Refresh Interval</label>
-                       <description>The refresh interval to poll Netatmo API (in ms).</description>
-                       <default>300000</default>
-                       <advanced>true</advanced>
+                       <description>The refresh interval to poll Netatmo API (in seconds).</description>
+                       <default>180</default>
                </parameter>
        </config-description>
 
-       <config-description uri="thing-type:netatmo:camera">
+       <config-description uri="netatmo:configurable">
                <parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
-                       <label>Camera ID</label>
-                       <description>Camera MAC Address</description>
-               </parameter>
-
-               <parameter name="parentId" type="text" required="true">
-                       <label>Home ID</label>
-                       <description>UUID of the home hosting the camera</description>
-               </parameter>
-       </config-description>
-
-       <config-description uri="thing-type:netatmo:nawelcomeperson">
-               <parameter name="id" type="text" required="true">
-                       <label>Person ID</label>
-                       <description>UUID of the Person</description>
+                       <label>Equipment ID</label>
+                       <description>ID of the device (MAC address).</description>
                </parameter>
 
-               <parameter name="parentId" type="text" required="true">
-                       <label>Home ID</label>
-                       <description>UUID of the home</description>
+               <parameter name="refreshInterval" type="integer" min="20" unit="s">
+                       <label>Refresh Interval</label>
+                       <description>The refresh interval to poll Netatmo API (in seconds).</description>
+                       <default>180</default>
                </parameter>
        </config-description>
 
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties
new file mode 100644 (file)
index 0000000..cc52514
--- /dev/null
@@ -0,0 +1,351 @@
+# binding
+
+binding.netatmo.name = Netatmo Binding
+binding.netatmo.description = The Netatmo binding integrates Weather Station with companion modules, Healthy Home Coach, Thermostat Plug and Welcome Camera.
+
+# binding config
+
+binding.config.netatmo.features.label = Feature Area
+binding.config.netatmo.features.description = Defines the set of capabilities you want to operate on.
+binding.config.netatmo.features.option.AIR_CARE = Air Care
+binding.config.netatmo.features.option.WEATHER = Weather
+binding.config.netatmo.features.option.ENERGY = Energy
+binding.config.netatmo.features.option.SECURITY = Security
+binding.config.netatmo.readFriends.label = Access Guests
+binding.config.netatmo.readFriends.description = For Weather Stations: A friend gave you access to their Netatmo Weather Station.
+
+# channel group types
+
+channel-group-type.netatmo.airquality-extended.label = Air Quality
+channel-group-type.netatmo.airquality.label = Air Quality
+channel-group-type.netatmo.battery-extended.label = Battery
+channel-group-type.netatmo.battery.label = Battery
+channel-group-type.netatmo.energy.label = Home Energy
+channel-group-type.netatmo.energy.channel.end.label = Mode End
+channel-group-type.netatmo.energy.channel.end.description = End time of the currently applied setpoint.
+channel-group-type.netatmo.humidity.label = Humidity
+channel-group-type.netatmo.last-event.label = Last Event
+channel-group-type.netatmo.last-event.channel.local-video-url.label = Video Local URL
+channel-group-type.netatmo.last-event.channel.local-video-url.description = Local URL of the event recording.
+channel-group-type.netatmo.last-event.channel.time.label = Event Timestamp
+channel-group-type.netatmo.last-event.channel.time.description = Moment when event occurred.
+channel-group-type.netatmo.last-event.channel.vpn-video-url.label = Video VPN URL
+channel-group-type.netatmo.last-event.channel.vpn-video-url.description = URL of the event recording through Netatmo VPN.
+channel-group-type.netatmo.live.label = Live Monitoring
+channel-group-type.netatmo.live.channel.local-picture-url.label = Live Snapshot Local URL
+channel-group-type.netatmo.live.channel.local-picture-url.description = Local URL of the live snapshot for this camera.
+channel-group-type.netatmo.live.channel.local-stream-url.label = Live Stream Local URL
+channel-group-type.netatmo.live.channel.local-stream-url.description = Local URL of the live stream for this camera.
+channel-group-type.netatmo.live.channel.vpn-picture-url.label = Live Snapshot VPN URL
+channel-group-type.netatmo.live.channel.vpn-picture-url.description = URL of the live snapshot for this camera through Netatmo VPN.
+channel-group-type.netatmo.live.channel.vpn-stream-url.label = Live Stream VPN URL
+channel-group-type.netatmo.live.channel.vpn-stream-url.description = URL of the live stream for this camera through Netatmo VPN.
+channel-group-type.netatmo.location.label = Location
+channel-group-type.netatmo.noise.label = Noise
+channel-group-type.netatmo.person-event.label = Last Event
+channel-group-type.netatmo.person-event.channel.message.description = Last event message from this person.
+channel-group-type.netatmo.person-event.channel.snapshot.description = Picture of the last event for this person.
+channel-group-type.netatmo.person-event.channel.snapshot-url.description = URL for the picture of the last event for this person.
+channel-group-type.netatmo.person-event.channel.time.label = Person Timestamp
+channel-group-type.netatmo.person-event.channel.time.description = Moment of the last event for this person.
+channel-group-type.netatmo.person.label = Person
+channel-group-type.netatmo.person.channel.last-seen.label = Last Seen
+channel-group-type.netatmo.person.channel.last-seen.description = Moment when this person was last seen.
+channel-group-type.netatmo.plug.label = Thermostat Plug
+channel-group-type.netatmo.presence.label = Presence Camera
+channel-group-type.netatmo.pressure-extended.label = Pressure
+channel-group-type.netatmo.pressure-extended.channel.trend.label = Pressure Trend
+channel-group-type.netatmo.pressure.label = Pressure
+channel-group-type.netatmo.rain.label = Rain gauge
+channel-group-type.netatmo.rain.channel.sum-1.label = Rain 1h
+channel-group-type.netatmo.rain.channel.sum-1.description = Quantity of water over last hour.
+channel-group-type.netatmo.rain.channel.sum-24.label = Rain 24h
+channel-group-type.netatmo.rain.channel.sum-24.description = Quantity of water during the current day.
+channel-group-type.netatmo.room-properties.label = Room Status
+channel-group-type.netatmo.room-temperature.label = Room Temperature
+channel-group-type.netatmo.security.label = Home Security
+channel-group-type.netatmo.setpoint.label = Setpoint
+channel-group-type.netatmo.setpoint.channel.end.label = Setpoint End
+channel-group-type.netatmo.setpoint.channel.end.description = End time of the currently applied setpoint.
+channel-group-type.netatmo.setpoint.channel.start.label = Setpoint Start
+channel-group-type.netatmo.setpoint.channel.start.description = Start time of the currently applied setpoint.
+channel-group-type.netatmo.signal.label = Signal
+channel-group-type.netatmo.status.label = Camera Status
+channel-group-type.netatmo.temperature-extended.label = Temperature
+channel-group-type.netatmo.temperature-extended.channel.max-time.label = Today Max Timestamp
+channel-group-type.netatmo.temperature-extended.channel.max-time.description = Moment when temperature was measured at its maximum today.
+channel-group-type.netatmo.temperature-extended.channel.min-time.label = Today Min Timestamp
+channel-group-type.netatmo.temperature-extended.channel.min-time.description = Moment when temperature was measured at its minimum today.
+channel-group-type.netatmo.temperature-extended.channel.trend.label = Temperature Trend
+channel-group-type.netatmo.temperature-outside.label = Temperature
+channel-group-type.netatmo.temperature-outside.channel.max-time.label = Today Max Timestamp
+channel-group-type.netatmo.temperature-outside.channel.max-time.description = Moment when temperature was measured at its maximum today.
+channel-group-type.netatmo.temperature-outside.channel.min-time.label = Today Min Timestamp
+channel-group-type.netatmo.temperature-outside.channel.min-time.description = Moment when temperature was measured at its minimum today.
+channel-group-type.netatmo.temperature-outside.channel.trend.label = Temperature Trend
+channel-group-type.netatmo.temperature.label = Temperature
+channel-group-type.netatmo.temperature.channel.max-time.label = Today Max Timestamp
+channel-group-type.netatmo.temperature.channel.max-time.description = Moment when temperature was measured at its maximum today.
+channel-group-type.netatmo.temperature.channel.min-time.label = Today Min Timestamp
+channel-group-type.netatmo.temperature.channel.min-time.description = Moment when temperature was measured at its minimum today.
+channel-group-type.netatmo.th-properties.label = Thermostat
+channel-group-type.netatmo.timestamp-extended.label = Timestamp
+channel-group-type.netatmo.timestamp-extended.channel.last-seen.label = Last Seen
+channel-group-type.netatmo.timestamp-extended.channel.last-seen.description = Last time the module reported its presence.
+channel-group-type.netatmo.timestamp-extended.channel.measures.label = Measures Timestamp
+channel-group-type.netatmo.timestamp-extended.channel.measures.description = Moment of the last measures update.
+channel-group-type.netatmo.timestamp.label = Timestamp
+channel-group-type.netatmo.timestamp.channel.last-seen.label = Last Seen
+channel-group-type.netatmo.timestamp.channel.last-seen.description = Last time the module reported its presence.
+channel-group-type.netatmo.wind.label = Wind
+channel-group-type.netatmo.wind.channel.max-strength-date.label = Date Max Wind Strength
+channel-group-type.netatmo.wind.channel.max-strength-date.description = Moment when max wind strength was recorded.
+
+# channel types
+
+channel-type.netatmo.absolute-pressure.label = Absolute Pressure
+channel-type.netatmo.absolute-pressure.description = Pressure measured relative to a full vacuum.
+channel-type.netatmo.alim-status.label = Alim State
+channel-type.netatmo.alim-status.description = State of the power connector
+channel-type.netatmo.alim-status.state.option.ALIM_INCORRECT_POWER = Incorrect power adapter
+channel-type.netatmo.alim-status.state.option.ALIM_CORRECT_POWER = Correct power adapter
+channel-type.netatmo.anticipating-heating.label = Anticipated Heating
+channel-type.netatmo.anticipating-heating.description = Anticipates next scheduled setpoint.
+channel-type.netatmo.at-home.label = At Home
+channel-type.netatmo.at-home.description = Indicates if this person is known to be at home or not.
+channel-type.netatmo.avatar-picture-url.label = Avatar Picture URL
+channel-type.netatmo.avatar-picture-url.description = URL for the avatar of this person.
+channel-type.netatmo.avatar-picture.label = Avatar Picture
+channel-type.netatmo.avatar-picture.description = Avatar of this person.
+channel-type.netatmo.battery-status.label = Battery Status
+channel-type.netatmo.battery-status.description = Description of the battery status.
+channel-type.netatmo.camera-event.label = Camera Event
+channel-type.netatmo.camera-id.label = Camera ID
+channel-type.netatmo.camera-id.description = ID of the camera that triggered the event.
+channel-type.netatmo.co2.label = CO2
+channel-type.netatmo.co2.description = Air Quality indicator.
+channel-type.netatmo.dewpoint-depression.label = Dewpoint Depression
+channel-type.netatmo.dewpoint-depression.description = Difference between the temperature and the dewpoint.
+channel-type.netatmo.dewpoint.label = Dewpoint
+channel-type.netatmo.dewpoint.description = Temperature to which air must be cooled to become saturated with water vapor.
+channel-type.netatmo.energy-mode.label = House Mode
+channel-type.netatmo.energy-mode.description = Chosen mode for the house (schedule, away, frost guard, manual).
+channel-type.netatmo.energy-mode.state.option.SCHEDULE = Following a weekly schedule
+channel-type.netatmo.energy-mode.state.option.AWAY = Applying the -away- temperature as defined by the user
+channel-type.netatmo.energy-mode.state.option.FROST_GUARD = Frost-guard
+channel-type.netatmo.energy-mode.state.option.MANUAL = Applying a manually set temperature setpoint
+channel-type.netatmo.event-picture-url.label = Event Snapshot URL
+channel-type.netatmo.event-picture-url.description = Url of the event snapshot.
+channel-type.netatmo.event-picture.label = Event Snapshot
+channel-type.netatmo.event-picture.description = Capture image of the event.
+channel-type.netatmo.event-subtype.label = Event Sub Type
+channel-type.netatmo.event-subtype.description = Details of the event.
+channel-type.netatmo.event-subtype.state.option.SD_CARD_MISSING = Missing SD Card
+channel-type.netatmo.event-subtype.state.option.SD_CARD_INSERTED = SD Card inserted
+channel-type.netatmo.event-subtype.state.option.SD_CARD_FORMATTED = SD Card formated
+channel-type.netatmo.event-subtype.state.option.SD_CARD_WORKING = Working SD Card
+channel-type.netatmo.event-subtype.state.option.SD_CARD_DEFECTIVE = Defective SD Card
+channel-type.netatmo.event-subtype.state.option.SD_CARD_INCOMPATIBLE_SPEED = Incompatible SD Card speed
+channel-type.netatmo.event-subtype.state.option.SD_CARD_INSUFFICIENT_SPACE = Insufficient SD Card space
+channel-type.netatmo.event-subtype.state.option.ALIM_INCORRECT_POWER = Incorrect power adapter
+channel-type.netatmo.event-subtype.state.option.ALIM_CORRECT_POWER = Correct power adapter
+channel-type.netatmo.event-subtype.state.option.PERSON_ARRIVAL = Person arrived
+channel-type.netatmo.event-subtype.state.option.PERSON_DEPARTURE = Person has left
+channel-type.netatmo.event-subtype.state.option.PERSON_SEEN = Person has been seen
+channel-type.netatmo.event-subtype.state.option.MOVEMENT_HUMAN = Human seen
+channel-type.netatmo.event-subtype.state.option.MOVEMENT_VEHICLE = Car seen
+channel-type.netatmo.event-subtype.state.option.MOVEMENT_ANIMAL = Animal seen
+channel-type.netatmo.event-type.label = Event Type
+channel-type.netatmo.event-type.description = Description of the event.
+channel-type.netatmo.event-type.state.option.PERSON = Face detected
+channel-type.netatmo.event-type.state.option.PERSON_AWAY = Person has left home
+channel-type.netatmo.event-type.state.option.PERSON_HOME = Person is at home
+channel-type.netatmo.event-type.state.option.OUTDOOR = Motion detected by Presence
+channel-type.netatmo.event-type.state.option.MOVEMENT = Motion detected
+channel-type.netatmo.event-type.state.option.HUMAN = Human seen
+channel-type.netatmo.event-type.state.option.ANIMAL = Animal seen
+channel-type.netatmo.event-type.state.option.NEW_MODULE = New Module has been paired
+channel-type.netatmo.event-type.state.option.MODULE_CONNECT = Module is connected with the Indoor Camera
+channel-type.netatmo.event-type.state.option.MODULE_DISCONNECT = Module lost its connection with the Indoor Camera
+channel-type.netatmo.event-type.state.option.MODULE_LOW_BATTERY = Module's battery is low
+channel-type.netatmo.event-type.state.option.MODULE_END_UPDATE = Module's firmware update is over
+channel-type.netatmo.event-type.state.option.CONNECTION = Camera connected to Netatmo
+channel-type.netatmo.event-type.state.option.DISCONNECTION = Camera disconnected from Netatmo
+channel-type.netatmo.event-type.state.option.ON = Monitoring activated
+channel-type.netatmo.event-type.state.option.OFF = Monitoring stopped
+channel-type.netatmo.event-type.state.option.BOOT = Camera booting
+channel-type.netatmo.event-type.state.option.SD = SD card status changed
+channel-type.netatmo.event-type.state.option.ALIM = Power status changed
+channel-type.netatmo.floodlight-mode.label = Floodlight
+channel-type.netatmo.floodlight-mode.description = State of the floodlight (On/Off/Auto)
+channel-type.netatmo.floodlight-mode.state.option.ON = On
+channel-type.netatmo.floodlight-mode.state.option.OFF = Off
+channel-type.netatmo.floodlight-mode.state.option.AUTO = Auto
+channel-type.netatmo.gust-angle.label = Gust Angle
+channel-type.netatmo.gust-angle.description = Direction of the last 5 minutes highest gust wind
+channel-type.netatmo.gust-strength.label = Gust Strength
+channel-type.netatmo.gust-strength.description = Speed of the last 5 minutes highest gust wind
+channel-type.netatmo.health-index.label = Health Index
+channel-type.netatmo.health-index.description = Health index (healthy, fine, fair, poor, unhealthy).
+channel-type.netatmo.health-index.state.option.0 = Healthy
+channel-type.netatmo.health-index.state.option.1 = Fine
+channel-type.netatmo.health-index.state.option.2 = Fair
+channel-type.netatmo.health-index.state.option.3 = Poor
+channel-type.netatmo.health-index.state.option.4 = Unhealthy
+channel-type.netatmo.heat-index.label = Heat Index
+channel-type.netatmo.heat-index.description = Apparent computed temperature (based on temperature and humidity).
+channel-type.netatmo.heating-status.label = Heating Status
+channel-type.netatmo.heating-status.description = Is the furnace currently heating?
+channel-type.netatmo.home-event.label = Home Event
+channel-type.netatmo.humidex-scale.label = Humidex Appreciation
+channel-type.netatmo.humidex-scale.description = Appreciation of the Humidex.
+channel-type.netatmo.humidex-scale.state.option.0 = Comfortable
+channel-type.netatmo.humidex-scale.state.option.1 = Some discomfort
+channel-type.netatmo.humidex-scale.state.option.2 = Great discomfort
+channel-type.netatmo.humidex-scale.state.option.3 = Dangerous
+channel-type.netatmo.humidex-scale.state.option.4 = Very dangerous
+channel-type.netatmo.humidex.label = Humidex
+channel-type.netatmo.humidex.description = Computed Humidex: felt temperature.
+channel-type.netatmo.live-picture-url.label = Live Snapshot URL
+channel-type.netatmo.live-picture-url.description = URL of the live snapshot for this camera (need scope access_camera).
+channel-type.netatmo.live-picture.label = Live Snapshot
+channel-type.netatmo.live-picture.description = Camera Live Snapshot.
+channel-type.netatmo.live-stream-url.label = Live Stream URL
+channel-type.netatmo.live-stream-url.description = URL of the live stream for this camera.
+channel-type.netatmo.location.label = Location
+channel-type.netatmo.location.description = Location of the device
+channel-type.netatmo.max-temp.label = Max Temp
+channel-type.netatmo.max-temp.description = Maximum Temperature on current day.
+channel-type.netatmo.max-wind-strength.label = Max Wind Strength
+channel-type.netatmo.max-wind-strength.description = Maximum wind strength recorded
+channel-type.netatmo.message.label = Message
+channel-type.netatmo.message.description = Message sent by Netatmo corresponding to given event.
+channel-type.netatmo.min-temp.label = Min Temp
+channel-type.netatmo.min-temp.description = Minimum Temperature on current day
+channel-type.netatmo.monitoring-status.label = Monitoring
+channel-type.netatmo.monitoring-status.description = Monitoring state of the camera
+channel-type.netatmo.noise.label = Noise
+channel-type.netatmo.noise.description = Current Noise Level.
+channel-type.netatmo.person-count.label = Person Count
+channel-type.netatmo.person-count.description = Total number of persons that are at home.
+channel-type.netatmo.person-id.label = Person ID
+channel-type.netatmo.planning.label = Planning
+channel-type.netatmo.planning.description = Planning currently applied when following weekly schedule.
+channel-type.netatmo.rain-intensity.label = Rain Intensity
+channel-type.netatmo.rain-intensity.description = Current precipitation intensity.
+channel-type.netatmo.rain-quantity.label = Rain Quantity
+channel-type.netatmo.rain-quantity.description = Quantity of water over the period.
+channel-type.netatmo.room-heating-percent.label = Heating Power
+channel-type.netatmo.room-heating-percent.description = Percentage of heating power.
+channel-type.netatmo.rssi.label = Signal
+channel-type.netatmo.rssi.description = Signal strength indicator.
+channel-type.netatmo.sd-card-status.label = SD Card State
+channel-type.netatmo.sd-card-status.description = State of the SD card
+channel-type.netatmo.sd-card-status.state.option.SD_CARD_MISSING = Missing SD Card
+channel-type.netatmo.sd-card-status.state.option.SD_CARD_INSERTED = SD Card inserted
+channel-type.netatmo.sd-card-status.state.option.SD_CARD_FORMATTED = SD Card formated
+channel-type.netatmo.sd-card-status.state.option.SD_CARD_WORKING = Working SD Card
+channel-type.netatmo.sd-card-status.state.option.SD_CARD_DEFECTIVE = Defective SD Card
+channel-type.netatmo.sd-card-status.state.option.SD_CARD_INCOMPATIBLE_SPEED = Incompatible SD Card speed
+channel-type.netatmo.sd-card-status.state.option.SD_CARD_INSUFFICIENT_SPACE = Insufficient SD Card space
+channel-type.netatmo.setpoint-duration.label = Setpoint Duration
+channel-type.netatmo.setpoint-duration.description = Default duration of manual setpoint changes.
+channel-type.netatmo.setpoint.label = Setpoint
+channel-type.netatmo.setpoint.description = Thermostat temperature setpoint.
+channel-type.netatmo.th-mode.label = Thermostat Mode
+channel-type.netatmo.th-mode.description = Chosen thermostat mode (home, frost guard, manual, max).
+channel-type.netatmo.th-mode.state.option.HOME = Home
+channel-type.netatmo.th-mode.state.option.FROST_GUARD = Frost Guard
+channel-type.netatmo.th-mode.state.option.MANUAL = Manual
+channel-type.netatmo.th-mode.state.option.MAX = Max
+channel-type.netatmo.timestamp-advanced.label = Timestamp
+channel-type.netatmo.timestamp-advanced.description = Moment when data was measured.
+channel-type.netatmo.timestamp.label = Timestamp
+channel-type.netatmo.timestamp.description = Moment when data was measured.
+channel-type.netatmo.trend.label = Trend
+channel-type.netatmo.trend.description = Evolution of the measure over time.
+channel-type.netatmo.trend.state.option.UP = Up
+channel-type.netatmo.trend.state.option.STABLE = Stable
+channel-type.netatmo.trend.state.option.DOWN = Down
+channel-type.netatmo.unknown-person-count.label = Unknown Persons Count
+channel-type.netatmo.unknown-person-count.description = Total number of unknown persons that are at home.
+channel-type.netatmo.unknown-person-picture.label = Unknown Person Snapshot
+channel-type.netatmo.unknown-person-picture.description = Snapshot of unknown person that is at home.
+channel-type.netatmo.video-status.label = Video Status
+channel-type.netatmo.video-status.description = Status of the video (recording, deleted or available).
+channel-type.netatmo.video-status.state.option.RECORDING = Recording
+channel-type.netatmo.video-status.state.option.DELETED = Deleted
+channel-type.netatmo.video-status.state.option.AVAILABLE = Available
+channel-type.netatmo.video-url.label = Video URL
+channel-type.netatmo.video-url.description = URL of the event recording.
+channel-type.netatmo.window-open.label = Window Status
+channel-type.netatmo.window-open.description = Windows of the room are opened.
+
+# channel types config
+
+channel-type.config.netatmo.live-stream-url.quality.label = Quality Level
+channel-type.config.netatmo.live-stream-url.quality.description = Defines quality level of the feed (the higher the more bandwidth)
+channel-type.config.netatmo.live-stream-url.quality.option.low = Low Quality
+channel-type.config.netatmo.live-stream-url.quality.option.poor = Poor Quality
+channel-type.config.netatmo.live-stream-url.quality.option.high = High Quality
+
+# thing types
+
+thing-type.netatmo.account.label = Netatmo Account
+thing-type.netatmo.account.description = This bridge represents an account, gateway to Netatmo API.
+thing-type.netatmo.doorbell.label = Smart Video Doorbell
+thing-type.netatmo.doorbell.description = The Netatmo Smart Video Doorbell device.
+thing-type.netatmo.home-coach.label = Healthy Home Coach
+thing-type.netatmo.home-coach.description = Healthy home coach reporting health-index, temperature, humidity, pressure, air quality and sound level.
+thing-type.netatmo.home.label = Home
+thing-type.netatmo.home.description = A home hosting Security or Energy devices and modules.
+thing-type.netatmo.indoor.label = Additional Module
+thing-type.netatmo.indoor.description = Additional indoor module reporting temperature, humidity and CO2 level.
+thing-type.netatmo.outdoor.label = Outdoor Module
+thing-type.netatmo.outdoor.description = Outdoor module reporting temperature and humidity.
+thing-type.netatmo.person.label = Person
+thing-type.netatmo.person.description = A person known by your Netatmo system.
+thing-type.netatmo.plug.label = Relay Plug
+thing-type.netatmo.plug.description = The relay connected to the boiler controlling a Thermostat and zero or more valves.
+thing-type.netatmo.presence.label = Smart Outdoor Camera
+thing-type.netatmo.presence.description = The Netatmo Smart Outdoor Camera (Presence) camera with or without siren.
+thing-type.netatmo.rain.label = Rain Gauge
+thing-type.netatmo.rain.description = Rain Gauge measuring precipitation.
+thing-type.netatmo.room.label = Room
+thing-type.netatmo.room.description = A room in your house.
+thing-type.netatmo.siren.label = Siren Module
+thing-type.netatmo.siren.description = The Netatmo Smart Indoor Siren.
+thing-type.netatmo.thermostat.label = Thermostat Module
+thing-type.netatmo.thermostat.description = The Thermostat device placed in a given room.
+thing-type.netatmo.valve.label = Radiator valve
+thing-type.netatmo.valve.description = A valve controlling a radiator.
+thing-type.netatmo.weather-station.label = Weather Station
+thing-type.netatmo.weather-station.description = Main indoor module reporting temperature, humidity, pressure, air quality and sound level.
+thing-type.netatmo.welcome.label = Smart Indoor Camera
+thing-type.netatmo.welcome.description = The Netatmo Smart Indoor Camera (Welcome).
+thing-type.netatmo.wind.label = Wind Gauge Module
+thing-type.netatmo.wind.description = Wind sensor reporting wind angle and strength.
+
+# error messages
+
+conf-error-no-client-id = Cannot connect to Netatmo bridge as no client id is available in the configuration
+conf-error-no-client-secret = Cannot connect to Netatmo bridge as no client secret is available in the configuration
+conf-error-no-username = Cannot connect to Netatmo bridge as no username is available in the configuration
+conf-error-no-password = Cannot connect to Netatmo bridge as no password is available in the configuration
+status-bridge-offline = Bridge is not connected to Netatmo API
+device-not-connected = Thing is not reachable
+data-over-limit = Data seems quite old
+request-time-out = Request timed out - will attempt reconnection later
+
+# actions
+
+actionInputSetpointLabel = Setpoint
+actionInputSetpointDesc = The temperature setpoint
+actionInputEndtimeLabel = Endtime
+actionInputEndtimeDesc = Time the setpoint should end
+actionInputModeLabel = Mode
+actionInputModeDesc = The mode to set: MANUAL, SCHEDULE or FG (Frost-Guard)
+actionLabel = send a set room thermpoint command
+actionDesc = Send set room thermpoint command with endtime.
+reconnectApiLabel = Reconnect API
+reconnectApiDesc = Reopens the Netatmo API session.
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_de.properties b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_de.properties
deleted file mode 100644 (file)
index 8c4289a..0000000
+++ /dev/null
@@ -1,334 +0,0 @@
-# binding
-binding.netatmo.name = Netatmo Binding
-binding.netatmo.description = Dieses Binding integriert die smarte Wetterstation, die Zusatzmodule wie Regenmesser und Windmesser, als auch das smarte Thermostat und den Healthy Home Coach.
-
-# bridge types
-thing-type.netatmo.netatmoapi.label = Netatmo API
-thing-type.netatmo.netatmoapi.description = Netatmo API
-
-# bridge types config
-thing-type.config.netatmo.bridge.clientId.label = Client id
-thing-type.config.netatmo.bridge.clientId.description = Client id der APP, die unter https://dev.netatmo.com/myaccount/createanapp erstellt wurde.
-
-thing-type.config.netatmo.bridge.clientSecret.label = Client secret
-thing-type.config.netatmo.bridge.clientSecret.description = Client secret der APP, die unter https://dev.netatmo.com/myaccount/createanapp erstellt wurde.
-
-thing-type.config.netatmo.bridge.username.label = Benutzer
-thing-type.config.netatmo.bridge.username.description = Benutzer zur Authentifizierung an der Netatmo API.
-
-thing-type.config.netatmo.bridge.password.label = Passwort
-thing-type.config.netatmo.bridge.password.description = Passwort zur Authentifizierung an der Netatmo API.
-
-thing-type.config.netatmo.bridge.readStation.label = Wetterstation API
-thing-type.config.netatmo.bridge.readStation.description = Aktiviert den Zugriff auf die Wetterstation API (lesend).
-
-thing-type.config.netatmo.bridge.readHealthyHomeCoach.label = Healthy Home Coach API
-thing-type.config.netatmo.bridge.readHealthyHomeCoach.description = Aktiviert den Zugriff auf die Healthy Home Coach API (lesend).
-
-thing-type.config.netatmo.bridge.readThermostat.label = Thermostat API
-thing-type.config.netatmo.bridge.readThermostat.description = Aktiviert den Zugriff auf die Thermostat API (lesend und schreibend).
-
-#thing-type.config.netatmo.bridge.readWelcome.label = Access Welcome camera
-#thing-type.config.netatmo.bridge.readWelcome.description = Read and Access Welcome camera's data.
-
-#thing-type.config.netatmo.bridge.readPresence.label = Access Presence camera
-#thing-type.config.netatmo.bridge.readPresence.description = Read and Access Presence camera's data.
-
-#thing-type.config.netatmo.bridge.webHookUrl.label = Webhook address
-#thing-type.config.netatmo.bridge.webHookUrl.description = Protocol, public IP and port to access OH2 server from Internet.
-
-#thing-type.config.netatmo.bridge.reconnectInterval.label = Reconnect Interval
-#thing-type.config.netatmo.bridge.reconnectInterval.description = The reconnection interval to Netatmo API (in s).
-
-# thing types
-thing-type.netatmo.NAMain.label = Haupt-Indoor-Modul
-thing-type.netatmo.NAMain.description = Das Haupt-Indoor-Modul liefert Daten wie z.B. Temperatur, Luftdruck, Luftfeuchtigkeit, CO2-Gehalt und Lautstärke.
-
-thing-type.netatmo.NAModule1.label = Outdoor-Modul
-thing-type.netatmo.NAModule1.description = Das Outdoor-Modul liefert Daten wie z.B. Temperatur und Luftdruck.
-
-thing-type.netatmo.NAModule2.label = Windmesser
-thing-type.netatmo.NAModule2.description = Der Windmesser liefert Daten wie z.B. Windrichtung und Windstärke.
-
-thing-type.netatmo.NAModule3.label = Regenmesser
-thing-type.netatmo.NAModule3.description = Der Regenmesser liefert Daten wie z.B. Echtzeit-Messungen zur Intensität des Regens.
-
-thing-type.netatmo.NAModule4.label = Zusatz-Indoor-Modul
-thing-type.netatmo.NAModule4.description = Das Zusatz-Indoor-Modul liefert Daten wie z.B. Temperatur, Luftdruck, Luftfeuchtigkeit und CO2-Gehalt.
-
-thing-type.netatmo.NAPlug.label = Thermostat Relais
-thing-type.netatmo.NAPlug.description = Das Thermostat Relais liefert Daten des Heizkessels.
-
-thing-type.netatmo.NATherm1.label = Heizkörperthermostat
-thing-type.netatmo.NATherm1.description = Das Heizkörperthermostat dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur.
-
-thing-type.netatmo.NHC.label = Healthy Home Coach
-thing-type.netatmo.NHC.description = Der Healthy Home Coach liefert Daten wie z.B. Temperatur, Luftdruck, Luftfeuchtigkeit, CO2-Gehalt und Lautstärke.
-
-#thing-type.netatmo.NAWelcomeHome.label = Welcome Home
-#thing-type.netatmo.NAWelcomeHome.description = This represents a home hosting a Welcome Camera.
-
-#thing-type.netatmo.NACamera.label = Welcome Camera
-#thing-type.netatmo.NACamera.description = This represents a welcome camera at home.
-
-#thing-type.netatmo.NAWelcomePerson.label = Welcome Person
-#thing-type.netatmo.NAWelcomePerson.description = This represents a person at home.
-
-# thing types config
-thing-type.config.netatmo.station.id.label = MAC-Adresse Modul
-thing-type.config.netatmo.station.id.description = MAC-Adresse des Haupt-Indoor-Moduls.
-
-thing-type.config.netatmo.module.id.label = MAC-Adresse Zusatzmodule
-thing-type.config.netatmo.module.id.description = MAC-Adresse des Zusatzmoduls.
-
-thing-type.config.netatmo.module.parentId.label = MAC-Adresse Haupt-Indoor-Modul
-thing-type.config.netatmo.module.parentId.description = MAC-Adresse des Haupt-Indoor-Moduls.
-
-#thing-type.config.netatmo.plug.id.label = Equipment ID
-#thing-type.config.netatmo.plug.id.description = Id of the Device (mac address).
-
-#thing-type.config.netatmo.natherm1.id.label = Module ID
-#thing-type.config.netatmo.natherm1.id.description = Id of the Module.
-
-#thing-type.config.netatmo.natherm1.parentId.label = Device ID
-#thing-type.config.netatmo.natherm1.parentId.description = Id of the main device.
-
-#thing-type.config.netatmo.natherm1.setpointDefaultDuration.label = Setpoint duration
-#thing-type.config.netatmo.natherm1.setpointDefaultDuration.description = Default duration of thermostat change when force to max or manual.
-
-#thing-type.config.netatmo.welcomehome.id.label = Home ID
-#thing-type.config.netatmo.welcomehome.id.description = UUID of the home
-
-#thing-type.config.netatmo.welcomehome.refreshInterval.label = Refresh Interval
-#thing-type.config.netatmo.welcomehome.refreshInterval.description = The refresh interval to poll Netatmo API (in ms).
-
-#thing-type.config.netatmo.camera.id.label = Camera ID
-#thing-type.config.netatmo.camera.id.description = Camera MAC Address
-
-#thing-type.config.netatmo.camera.parentId.label = Home ID
-#thing-type.config.netatmo.camera.parentId.description = UUID of the home hosting the camera
-
-#thing-type.config.netatmo.nawelcomeperson.id.label = Person ID
-#thing-type.config.netatmo.nawelcomeperson.id.description = UUID of the Person
-
-#thing-type.config.netatmo.nawelcomeperson.parentId.label = Home ID
-#thing-type.config.netatmo.nawelcomeperson.parentId.description = UUID of the home
-
-# channel types
-channel-type.netatmo.co2.label = CO2-Gehalt
-channel-type.netatmo.co2.description = Zeigt den aktuellen CO2-Gehalt an.
-
-channel-type.netatmo.temperature.label = Temperatur
-channel-type.netatmo.temperature.description = Zeigt die aktuelle Temperatur an.
-
-channel-type.netatmo.minTemp.label = Min. Temperatur
-channel-type.netatmo.minTemp.description = Zeigt die minimale Temperatur des aktuellen Tages an.
-
-channel-type.netatmo.maxTemp.label = Max. Temperatur
-channel-type.netatmo.maxTemp.description = Zeigt die maximale Temperatur des aktuellen Tages an.
-
-channel-type.netatmo.dateMinTemp.label = Zeitpunkt Min. Temperatur
-channel-type.netatmo.dateMinTemp.description = Zeigt den Zeitpunkt an, an dem die minimale Temperatur gemessen wurde.
-
-channel-type.netatmo.dateMaxTemp.label = Zeitpunkt Max. Temperatur
-channel-type.netatmo.dateMaxTemp.description = Zeigt den Zeitpunkt an, an dem die maximale Temperatur gemessen wurde.
-
-channel-type.netatmo.temperatureTrend.label = Temperaturtrend
-channel-type.netatmo.temperatureTrend.description = Zeigt den Temperaturtrend (z.B. "Steigend", "Stabil" oder "Fallend") an.
-channel-type.netatmo.temperatureTrend.state.option.up = Steigend
-channel-type.netatmo.temperatureTrend.state.option.stable = Stabil
-channel-type.netatmo.temperatureTrend.state.option.down = Fallend
-
-channel-type.netatmo.humidity.label = Luftfeuchtigkeit
-channel-type.netatmo.humidity.description = Zeigt die aktuelle Luftfeuchtigkeit an.
-
-channel-type.netatmo.humidex.label = Gef. Temperatur (Humidex)
-channel-type.netatmo.humidex.description = Zeigt die gefühlte Temperatur an. Basierend auf dem Humidex.
-
-channel-type.netatmo.heatIndex.label = Gef. Temperatur (Hitzeindex)
-channel-type.netatmo.heatIndex.description = Zeigt die gefühlte Temperatur an. Basierend auf dem Hitzeindex.
-
-channel-type.netatmo.dewPoint.label = Taupunkt
-channel-type.netatmo.dewPoint.description = Zeigt den Taupunkt an.
-
-channel-type.netatmo.dewPointDepression.label = Taupunktsdifferenz
-channel-type.netatmo.dewPointDepression.description = Zeigt die Taupunktsdifferenz an.
-
-channel-type.netatmo.noise.label = Sonometer
-channel-type.netatmo.noise.description = Zeigt die aktuelle Lautstärke an.
-
-channel-type.netatmo.pressure.label = Luftdruck
-channel-type.netatmo.pressure.description = Zeigt den aktuellen Luftdruck an.
-
-channel-type.netatmo.pressureTrend.label = Luftdrucktrend
-channel-type.netatmo.pressureTrend.description = Zeigt den Luftdrucktrend an (z.B. "Steigend", "Stabil" oder "Fallend").
-channel-type.netatmo.pressureTrend.state.option.up = Steigend
-channel-type.netatmo.pressureTrend.state.option.stable = Stabil
-channel-type.netatmo.pressureTrend.state.option.down = Fallend
-
-channel-type.netatmo.absolutePressure.label = Absoluter Luftdruck
-channel-type.netatmo.absolutePressure.description = Zeigt den absoluten Luftdruck an.
-
-channel-type.netatmo.lastStatusStore.label = Letzte Übertragung
-channel-type.netatmo.lastStatusStore.description = Zeigt den Zeitpunkt der letzten Datenübertragung an.
-
-channel-type.netatmo.timeUtc.label = Letzte Messung
-channel-type.netatmo.timeUtc.description = Zeigt den Zeitpunkt der letzten Datenmessung an.
-
-channel-type.netatmo.lastMessage.label = Letzte Meldung
-channel-type.netatmo.lastMessage.description = Zeigt den Zeitpunkt der letzten Meldung an.
-
-channel-type.netatmo.location.label = Standort
-channel-type.netatmo.location.description = Zeigt die geographischen Koordinaten (Breitengrad, Längengrad, Höhe über dem Meerespiegel) (in °N, °W, m) an.
-
-channel-type.netatmo.rain.label = Niederschlag
-channel-type.netatmo.rain.description = Zeigt den aktuellen Niederschlag an.
-
-channel-type.netatmo.rain1.label = Kumulierter Niederschlag (1h)
-channel-type.netatmo.rain1.description = Zeigt den kumulierten Niederschlag der letzten Stunde an.
-
-channel-type.netatmo.rain24.label = Kumulierter Niederschlag (24h)
-channel-type.netatmo.rain24.description = Zeigt den kumulierten Niederschlag der letzten 24h an.
-
-channel-type.netatmo.WindAngle.label = Windrichtung
-channel-type.netatmo.WindAngle.description = Zeigt die aktuelle Windrichtung an.
-
-channel-type.netatmo.WindStrength.label = Windgeschwindigkeit
-channel-type.netatmo.WindStrength.description = Zeigt die aktuelle Windgeschwindigkeit an.
-
-channel-type.netatmo.GustAngle.label = Böen Richtung
-channel-type.netatmo.GustAngle.description = Zeigt die aktuelle Böen Richtung an.
-
-channel-type.netatmo.GustStrength.label = Böen Geschwindigkeit
-channel-type.netatmo.GustStrength.description = Zeigt die aktuelle Böen Geschwindigkeit an.
-
-channel-type.netatmo.healthindex.label = Health Index
-channel-type.netatmo.healthindex.description = Zeigt den Health Index (z.B. "Gesund", "Gut", "Angemessen", "Schlecht" oder "Ungesund") an.
-channel-type.netatmo.healthindex.state.option.healthy = Gesund
-channel-type.netatmo.healthindex.state.option.fine = Gut
-channel-type.netatmo.healthindex.state.option.fair = Angemessen
-channel-type.netatmo.healthindex.state.option.poor = Schlecht
-channel-type.netatmo.healthindex.state.option.unhealthy = Ungesund
-
-#channel-type.netatmo.connectedBoiler.label = Plug Connected Boiler
-#channel-type.netatmo.connectedBoiler.description = 
-
-#channel-type.netatmo.lastPlugSeen.label = Last Plug Seen
-#channel-type.netatmo.lastPlugSeen.description = Last Plug Seen
-
-#channel-type.netatmo.lastBilan.label = Available Bilan
-#channel-type.netatmo.lastBilan.description = Month of the last available thermostat bilan
-
-#channel-type.netatmo.setpointTemp.label = Setpoint
-#channel-type.netatmo.setpointTemp.description = Thermostat temperature setpoint
-
-#channel-type.netatmo.setpointMode.label = Setpoint Mode
-#channel-type.netatmo.setpointMode.description = Chosen setpoint_mode (program, away, hg, manual, off, max)
-#channel-type.netatmo.setpointMode.state.option.program = Following a weekly schedule
-#channel-type.netatmo.setpointMode.state.option.away = Applying the -away- temperature as defined by the user
-#channel-type.netatmo.setpointMode.state.option.hg = Frost-guard
-#channel-type.netatmo.setpointMode.state.option.manual = Applying a manually set temperature setpoint
-#channel-type.netatmo.setpointMode.state.option.off = Currently off
-#channel-type.netatmo.setpointMode.state.option.max = Heating continuously
-
-#channel-type.netatmo.ThermRelayCmd.label = Heating status
-#channel-type.netatmo.ThermRelayCmd.description = Indicates whether the furnace is heating or not
-
-#channel-type.netatmo.ThermOrientation.label = Orientation
-#channel-type.netatmo.ThermOrientation.description = Physical orientation of the thermostat module
-
-#channel-type.netatmo.setpointEndTime.label = Setpoint end
-#channel-type.netatmo.setpointEndTime.description = Thermostat goes back to schedule after that timestamp.
-
-#channel-type.netatmo.homecity.label = City
-#channel-type.netatmo.homecity.description = City of the home
-
-#channel-type.netatmo.homecountry.label = Country
-#channel-type.netatmo.homecountry.description = Country of the home
-
-#channel-type.netatmo.hometimezone.label = Timezone
-#channel-type.netatmo.hometimezone.description = Timezone of the home
-
-#channel-type.netatmo.homepersoncount.label = Person counter
-#channel-type.netatmo.homepersoncount.description = Total number of Persons that are at home
-
-#channel-type.netatmo.homeunknowncount.label = Unknown Person counter
-#channel-type.netatmo.homeunknowncount.description = Count how many Unknown Persons are at home
-
-#channel-type.netatmo.type.label = Type
-#channel-type.netatmo.type.description = Type of event
-
-#channel-type.netatmo.time.label = Time
-#channel-type.netatmo.time.description = Time of occurrence of event
-
-#channel-type.netatmo.camera_id.label = Camera ID
-#channel-type.netatmo.camera_id.description = Camera that detected the event
-
-#channel-type.netatmo.person_id.label = Person ID
-#channel-type.netatmo.person_id.description = Id of the person the event is about (if any)
-
-#channel-type.netatmo.snapshot_url.label = Snapshot URL
-#channel-type.netatmo.snapshot_url.description = Url of the event snapshot
-
-#channel-type.netatmo.snapshot.label = Event Snapshot
-#channel-type.netatmo.snapshot.description = Event Snapshot
-
-#channel-type.netatmo.video_url.label = Video URL
-#channel-type.netatmo.video_url.description = URL of the event video
-
-#channel-type.netatmo.video_status.label = Video status
-#channel-type.netatmo.video_status.description = Status of the video (recording, deleted or available)
-
-#channel-type.netatmo.is_arrival.label = Is arrival
-#channel-type.netatmo.is_arrival.description = If person was considered "away" before being seen during this event
-
-#channel-type.netatmo.message.label = Message
-#channel-type.netatmo.message.description = Message sent by Netatmo corresponding to given event
-
-#channel-type.netatmo.sub_type.label = Sub Type
-#channel-type.netatmo.sub_type.description = Sub-type of SD and Alim events
-
-#channel-type.netatmo.status.label = State
-#channel-type.netatmo.status.description = State of the camera
-
-#channel-type.netatmo.sd_status.label = SD State
-#channel-type.netatmo.sd_status.description = State of the SD card
-
-#channel-type.netatmo.alim_status.label = Alim State
-#channel-type.netatmo.alim_status.description = State of the power connector
-
-#channel-type.netatmo.is_locale.label = Is local
-#channel-type.netatmo.is_locale.description = Indicates whether the camera is on the same network than the openHab Netatmo Binding
-
-#channel-type.netatmo.live_picture_url.label = Live snapshot URL
-#channel-type.netatmo.live_picture_url.description = Url of the live snapshot for this camera
-
-#channel-type.netatmo.live_picture.label = Live Snapshot
-#channel-type.netatmo.live_picture.description = Camera Live Snapshot
-
-#channel-type.netatmo.live_stream_url.label = Live stream URL
-#channel-type.netatmo.live_stream_url.description = Url of the live stream for this camera
-
-#channel-type.netatmo.last_seen.label = Last seen
-#channel-type.netatmo.last_seen.description = Time when this person was last seen
-
-#channel-type.netatmo.person_athome.label = At home
-#channel-type.netatmo.person_athome.description = Indicates if this person is known to be at home or not
-
-#channel-type.netatmo.person_eventmsg.label = Last Event message
-#channel-type.netatmo.person_eventmsg.description = Last Event message from this person
-
-#channel-type.netatmo.person_eventtime.label = Last Event time
-#channel-type.netatmo.person_eventtime.description = Last Event message time for this person
-
-#channel-type.netatmo.person_avatar_url.label = Avatar URL
-#channel-type.netatmo.person_avatar_url.description = URL for the avatar of this person
-
-#channel-type.netatmo.person_avatar.label = Avatar
-#channel-type.netatmo.person_avatar.description = Avatar of this person
-
-#channel-type.netatmo.person_event.label = Last Event Picture
-#channel-type.netatmo.person_event.description = Picture of the last event for this person
-
-#channel-type.netatmo.person_event_url.label = Last event URL
-#channel-type.netatmo.person_event_url.description = URL for the picture of the last event for this person
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_fr.properties b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_fr.properties
deleted file mode 100644 (file)
index 84d40b6..0000000
+++ /dev/null
@@ -1,378 +0,0 @@
-# binding
-binding.netatmo.name = Extension Netatmo
-binding.netatmo.description = L'extension Netatmo intègre le thermostat et la station météo Netatmo ainsi que les modules additionels.
-
-# bridge types
-thing-type.netatmo.netatmoapi.label = API Netatmo
-thing-type.netatmo.netatmoapi.description = Cet élément représente la passerelle avec l'API Netatmo.
-
-# bridge types configuration
-thing-type.config.netatmo.bridge.clientId.label = ID client
-thing-type.config.netatmo.bridge.clientId.description = ID clent fourni par l'application que vous avez créée sur http://dev.netatmo.com/createapp
-thing-type.config.netatmo.bridge.clientSecret.label = Secret client
-thing-type.config.netatmo.bridge.clientSecret.description = Secret client fourni par l'application que vous avez créée.
-
-thing-type.config.netatmo.bridge.username.label = Nom d'utilisateur
-thing-type.config.netatmo.bridge.username.description = Votre nom d'utilisateur (e-mail) pour l'API Netatmo.
-thing-type.config.netatmo.bridge.password.label = Mot de passe
-thing-type.config.netatmo.bridge.password.description = Votre mot de passe pour l'API Netatmo.
-
-thing-type.config.netatmo.bridge.readStation.label = Accès à la station météo
-thing-type.config.netatmo.bridge.readStation.description = Accède ou non aux données de la station météo.
-
-thing-type.config.netatmo.bridge.readHealthyHomeCoach.label = Accès au Healthy Home Coach
-thing-type.config.netatmo.bridge.readHealthyHomeCoach.description = Accède ou non aux données du Healthy Home Coach.
-
-thing-type.config.netatmo.bridge.readThermostat.label = Accès au thermostat
-thing-type.config.netatmo.bridge.readThermostat.description = Accède ou non aux données du thermostat.
-
-thing-type.config.netatmo.bridge.readWelcome.label = Accès à la Camera Welcome
-thing-type.config.netatmo.bridge.readWelcome.description = Accède ou non aux données de la caméra Welcome.
-
-thing-type.config.netatmo.bridge.readPresence.label = Accès à la Camera Presence
-thing-type.config.netatmo.bridge.readPresence.description = Accède ou non aux données de la caméra Presence.
-
-thing-type.config.netatmo.bridge.webHookUrl.label = Adresse Webhook
-thing-type.config.netatmo.bridge.webHookUrl.description = Protocole, IP publique et port pour l'accès au sereveur OH2 depuis l'Internet.
-
-thing-type.config.netatmo.bridge.reconnectInterval.label = Intervalle de reconnection
-thing-type.config.netatmo.bridge.reconnectInterval.description = L'intervalle de reconnection à l'API Netatmo (en s).
-
-# thing types
-thing-type.netatmo.NAMain.label = Station intérieure principale
-thing-type.netatmo.NAMain.description = Module capable de mesurer la température, l'humidité, la pression, la qualité de l'air et le niveau sonore.
-
-thing-type.netatmo.NAModule1.label = Module Extérieur
-thing-type.netatmo.NAModule1.description = Module extérieur capable de mesurer la température et l'humidité.
-
-thing-type.netatmo.NAModule2.label = Anémomètre
-thing-type.netatmo.NAModule2.description = Module extérieur dédié au vent capable de mesurer sa direction et sa force.
-
-thing-type.netatmo.NAModule3.label = Capteur de Pluie
-thing-type.netatmo.NAModule3.description = Module extérieur dédié à la mesure du niveau de précipitations.
-
-thing-type.netatmo.NAModule4.label = Module Additionel
-thing-type.netatmo.NAModule4.description = Module intérieur supplémentaire capable de mesurer la température, l'humidité et le niveau de CO2.
-
-thing-type.netatmo.NAPlug.label = Relais Thermostat
-thing-type.netatmo.NAPlug.description = Cet élément représente le relais communiquant avec le thermostat.
-
-thing-type.netatmo.NATherm1.label = Module Thermostat
-thing-type.netatmo.NATherm1.description = Cet élément représente le module thermostat permettant de règler la température.
-
-thing-type.netatmo.NHC.label = Healthy Home Coach
-thing-type.netatmo.NHC.description = Cet élément représente le module Healthy Home Coach capable de mesurer le niveau de confort dans la maison.
-
-thing-type.netatmo.NAWelcomeHome.label = Maison
-thing-type.netatmo.NAWelcomeHome.description = Cet élément représente une maison hébergeant une ou plusieurs caméras Netatmo.
-
-thing-type.netatmo.NACamera.label = Caméra Welcome
-thing-type.netatmo.NACamera.description = Cet élément représente une caméra Welcome de la maison.
-
-thing-type.netatmo.NAWelcomePerson.label = Personne
-thing-type.netatmo.NAWelcomePerson.description = Cet élément représente une personne de la maison.
-
-# thing type configuration
-thing-type.config.netatmo.station.id.label = ID équipement
-thing-type.config.netatmo.station.id.description = ID de l'équipement (adresse MAC)
-
-thing-type.config.netatmo.module.id.label = ID module
-thing-type.config.netatmo.module.id.description = ID du module
-
-thing-type.config.netatmo.module.parentId.label = ID équipement principal
-thing-type.config.netatmo.module.parentId.description = ID de l'équipement principal
-
-thing-type.config.netatmo.plug.id.label = ID équipement
-thing-type.config.netatmo.plug.id.description = ID de l'équipement (adresse MAC)
-
-thing-type.config.netatmo.natherm1.id.label = ID module
-thing-type.config.netatmo.natherm1.id.description = ID du module
-
-thing-type.config.netatmo.natherm1.parentId.label = ID équipement principal
-thing-type.config.netatmo.natherm1.parentId.description = ID de l'équipement principal
-
-thing-type.config.netatmo.natherm1.setpointDefaultDuration.label = Durée de consigne
-thing-type.config.netatmo.natherm1.setpointDefaultDuration.description = Durée par défaut de consigne hors planning
-
-thing-type.config.netatmo.welcomehome.id.label = ID maison
-thing-type.config.netatmo.welcomehome.id.description = ID de la maison
-
-thing-type.config.netatmo.welcomehome.refreshInterval.label = Fréquence de rafraîchissement
-thing-type.config.netatmo.welcomehome.refreshInterval.description = La fréquence d'interrogation de l'API Netatmo (en ms)
-
-thing-type.config.netatmo.camera.id.label = ID caméra
-thing-type.config.netatmo.camera.id.description = ID de la caméra (adresse MAC)
-
-thing-type.config.netatmo.camera.parentId.label = ID maison
-thing-type.config.netatmo.camera.parentId.description = ID de la maison hébergeant la caméra
-
-thing-type.config.netatmo.nawelcomeperson.id.label = ID personne
-thing-type.config.netatmo.nawelcomeperson.id.description = ID de la personne
-
-thing-type.config.netatmo.nawelcomeperson.parentId.label = ID maison
-thing-type.config.netatmo.nawelcomeperson.parentId.description = ID de la maison
-
-# channel types
-channel-type.netatmo.co2.label = CO2
-channel-type.netatmo.co2.description = Mesure de la qualité de l'air
-
-channel-type.netatmo.temperature.label = Température
-channel-type.netatmo.temperature.description = Mesure de la tempérture
-
-channel-type.netatmo.temperatureTrend.label = Tendance température
-channel-type.netatmo.temperatureTrend.description = Mesure de la tendance d'évolution de la tempérture
-channel-type.netatmo.temperatureTrend.state.option.up = A la hausse
-channel-type.netatmo.temperatureTrend.state.option.stable = Stable
-channel-type.netatmo.temperatureTrend.state.option.down = A la baisse
-
-channel-type.netatmo.noise.label = Niveau sonore
-channel-type.netatmo.noise.description = Mesure du niveau sonore de la pièce
-
-channel-type.netatmo.pressure.label = Pression atmosphérique 
-channel-type.netatmo.pressure.description = Mesure de la pression atmosphérique
-
-channel-type.netatmo.pressureTrend.label = Tendance pression atmosphérique 
-channel-type.netatmo.pressureTrend.description = Mesure de la tendance d'évolution de la pression atmosphérique sur les dernières 12 heures
-channel-type.netatmo.pressureTrend.state.option.up =  A la hausse
-channel-type.netatmo.pressureTrend.state.option.stable = Stable
-channel-type.netatmo.pressureTrend.state.option.down = A la baisse
-
-channel-type.netatmo.absolutePressure.label = Pression atmosphérique absolue
-channel-type.netatmo.absolutePressure.description = Mesure de la pression atmosphérique absolue
-
-channel-type.netatmo.timeUtc.label = Horodatage des mesures
-channel-type.netatmo.timeUtc.description = Date/Heure du dernier relevé de mesures
-
-channel-type.netatmo.humidity.label = Humidité
-channel-type.netatmo.humidity.description = Mesure du niveau d'hygrométrie
-
-channel-type.netatmo.humidex.label = Humidex
-channel-type.netatmo.humidex.description = Indice humidex calculé
-
-channel-type.netatmo.heatIndex.label = Indice de chaleur
-channel-type.netatmo.heatIndex.description = Indice de chaleur calculé
-
-channel-type.netatmo.dewPoint.label = Point de rosée
-channel-type.netatmo.dewPoint.description = Température du point de rosée
-
-channel-type.netatmo.dewPointDepression.label = Dépression du point de rosée
-channel-type.netatmo.dewPointDepression.description = Ecart entre la température actuelle et le point de rosée
-
-channel-type.netatmo.minTemp.label = Température min
-channel-type.netatmo.minTemp.description = Température minimale de la journée en cours
-
-channel-type.netatmo.maxTemp.label = Température max
-channel-type.netatmo.maxTemp.description = Température maximale de la journée en cours
-
-channel-type.netatmo.dateMinTemp.label = Horodatage température min
-channel-type.netatmo.dateMinTemp.description = Date/Heure de relevé de la température minimale pour la journée en cours
-
-channel-type.netatmo.dateMaxTemp.label = Horodatage température max
-channel-type.netatmo.dateMaxTemp.description = Date/Heure de relevé de la température maximale pour la journée en cours
-
-channel-type.netatmo.location.label = Localisation
-channel-type.netatmo.location.description = Localisation de la station Netatmo
-
-channel-type.netatmo.rain.label = Précipitations
-channel-type.netatmo.rain.description = Volume de précipitations relevé
-
-channel-type.netatmo.rain1.label = Précipitations 1h
-channel-type.netatmo.rain1.description = Volume de précipitations relevé durant la dernière heure
-
-channel-type.netatmo.rain24.label = Précipitations 24h
-channel-type.netatmo.rain24.description = Volume de précipitations relevé durant la dernière journée
-
-channel-type.netatmo.WindAngle.label = Direction du vent
-channel-type.netatmo.WindAngle.description = Direction moyenne du vent sur les 5 dernières minutes
-
-channel-type.netatmo.WindStrength.label = Force du vent
-channel-type.netatmo.WindStrength.description = Vitesse moyenne du vent sur les 5 dernières minutes
-
-channel-type.netatmo.GustAngle.label = Direction rafale de vent
-channel-type.netatmo.GustAngle.description = Direction moyenne des rafales de vent sur les 5 dernières minutes
-
-channel-type.netatmo.GustStrength.label = Force rafale de vent
-channel-type.netatmo.GustStrength.description = Vitesse moyenne des rafales de vent sur les 5 dernières minutes
-
-channel-type.netatmo.lastStatusStore.label = Dernière demande d'état
-channel-type.netatmo.lastStatusStore.description = Date/Heure de la dernière demande d'état
-
-channel-type.netatmo.lastMessage.label = Horodatage dernier message
-channel-type.netatmo.lastMessage.description = Date/Heure du dernier message émis par le module
-
-channel-type.netatmo.connectedBoiler.label = Relais connecté
-channel-type.netatmo.connectedBoiler.description = Indique si le relais est connecté ou non à une chaudière
-
-channel-type.netatmo.lastPlugSeen.label = Horodatage visibilité du relais
-channel-type.netatmo.lastPlugSeen.description = Date/Heure de dernière visibilité du module relais par le thermostat
-
-channel-type.netatmo.lastBilan.label = Bilan Economies d'Energie
-channel-type.netatmo.lastBilan.description = Mois du dernier bilan d'économies d'énergie disponible
-
-channel-type.netatmo.setpointTemp.label = Température de consigne
-channel-type.netatmo.setpointTemp.description = Température de consigne sélectionnée sur le thermostat
-
-channel-type.netatmo.setpointMode.label = Mode de consigne
-channel-type.netatmo.setpointMode.description = Mode de consigne choisi sur le thermostat (planning hebdo, absence, hors-gel, manuel, arrêt, en permanence)
-channel-type.netatmo.setpointMode.state.option.program = Suivi du planning hebdomadaire
-channel-type.netatmo.setpointMode.state.option.away = Température d'absence
-channel-type.netatmo.setpointMode.state.option.hg = Hors-gel
-channel-type.netatmo.setpointMode.state.option.manual = Température de consigne manuelle
-channel-type.netatmo.setpointMode.state.option.off = Arrêt
-channel-type.netatmo.setpointMode.state.option.max = Chauffage en permanence
-
-channel-type.netatmo.planning.label = Planning
-channel-type.netatmo.planning.description = Planification des plages de chauffe utilisée en mode suivi du planning
-
-channel-type.netatmo.ThermRelayCmd.label = Etat du chauffage
-channel-type.netatmo.ThermRelayCmd.description = Indique si le chauffage est en marche ou pas
-
-channel-type.netatmo.ThermOrientation.label = Orientation
-channel-type.netatmo.ThermOrientation.description = Orientation physique du module thermostat
-
-channel-type.netatmo.setpointEndTime.label = Heure fin de consigne
-channel-type.netatmo.setpointEndTime.description = Heure de retour au planning de chauffe
-
-channel-type.netatmo.healthindex.label = Indice de confort
-channel-type.netatmo.healthindex.description = Indice de confort (sain, agréable, correct, mauvais, malsain)
-channel-type.netatmo.healthindex.state.option.healthy = Sain
-channel-type.netatmo.healthindex.state.option.fine = Agréable
-channel-type.netatmo.healthindex.state.option.fair = Correct
-channel-type.netatmo.healthindex.state.option.poor = Mauvais
-channel-type.netatmo.healthindex.state.option.unhealthy = Malsain
-
-channel-type.netatmo.homecity.label = Ville
-channel-type.netatmo.homecity.description = Ville
-
-channel-type.netatmo.homecountry.label = Pays
-channel-type.netatmo.homecountry.description = Pays
-
-channel-type.netatmo.hometimezone.label = Fuseau horaire
-channel-type.netatmo.hometimezone.description = Fuseau horaire
-
-channel-type.netatmo.homepersoncount.label = Compteur de personnes
-channel-type.netatmo.homepersoncount.description = Nombre de personnes qui sont à la maison
-
-channel-type.netatmo.homeunknowncount.label = Compteur de personnes inconnues
-channel-type.netatmo.homeunknowncount.description = Nombre de personnes inconnues qui sont à la maison
-
-channel-type.netatmo.type.label = Type de l'évènement
-channel-type.netatmo.type.description = Type du dernier évènement
-
-channel-type.netatmo.time.label = Horodatage de l'évènement
-channel-type.netatmo.time.description = Date/Heure du dernier évènement
-
-channel-type.netatmo.camera_id.label = ID caméra
-channel-type.netatmo.camera_id.description = Caméra à l'origine du dernier évènement
-
-channel-type.netatmo.person_id.label = ID personne
-channel-type.netatmo.person_id.description = Id de la personne concernée par le dernier évènement
-
-channel-type.netatmo.snapshot_url.label = URL image capturée
-channel-type.netatmo.snapshot_url.description = Url de l'image capturée (quand une image est associée au dernier évènement) 
-
-channel-type.netatmo.snapshot.label = Image capturée
-channel-type.netatmo.snapshot.description = Image capturée (quand une image est associée au dernier évènement)
-
-channel-type.netatmo.video_url.label = URL vidéo capturée
-channel-type.netatmo.video_url.description = Url de la vidéo capturée (quand une vidéo est associée au dernier évènement)
-
-channel-type.netatmo.video_status.label = Etat de la vidéo
-channel-type.netatmo.video_status.description = Etat de la vidéo associée au dernier évènement (recording, deleted or available)
-
-channel-type.netatmo.is_arrival.label = Personne arrivant
-channel-type.netatmo.is_arrival.description = Si cet évènement indique la détection d'une personne qui était considérée comme absente auparavant
-
-channel-type.netatmo.message.label = Message de l'évènement
-channel-type.netatmo.message.description = Message correspondant au dernier évènement
-
-channel-type.netatmo.sub_type.label = Sous-type de l'évènement
-channel-type.netatmo.sub_type.description = Sous-type du dernier évènement (disponible uniquement pour certains évènements)
-
-channel-type.netatmo.status.label = Etat de la caméra
-channel-type.netatmo.status.description = Etat de la caméra
-
-channel-type.netatmo.sd_status.label = Etat de la carte SD
-channel-type.netatmo.sd_status.description = Etat de la carte SD
-
-channel-type.netatmo.alim_status.label = Etat de l'alimentation
-channel-type.netatmo.alim_status.description = Etat de l'alimentation
-
-channel-type.netatmo.is_locale.label = Caméra locale
-channel-type.netatmo.is_locale.description = Indique si la caméra est dans le même réseau local que le logiciel 
-
-channel-type.netatmo.live_picture_url.label = URL image en direct
-channel-type.netatmo.live_picture_url.description = Url de l'image en direct de la caméra
-
-channel-type.netatmo.live_picture.label = Image en direct
-channel-type.netatmo.live_picture.description = Image en direct de la caméra
-
-channel-type.netatmo.live_stream_url.label = URL flux vidéo en direct
-channel-type.netatmo.live_stream_url.description = Url du flux vidéo en direct de la caméra
-
-channel-type.netatmo.last_seen.label = Date dernière détection
-channel-type.netatmo.last_seen.description = Date où cette personne a été reconnue pour la dernière fois
-
-channel-type.netatmo.person_athome.label = A la maison
-channel-type.netatmo.person_athome.description = Indique si cette personne est connue comme étant ou non à la masioon
-
-channel-type.netatmo.person_eventmsg.label = Dernier message
-channel-type.netatmo.person_eventmsg.description = Dernier message relatif à cette personne
-
-channel-type.netatmo.person_eventtime.label = Date dernier message
-channel-type.netatmo.person_eventtime.description = Date du dernier message relatif à cette personne
-
-channel-type.netatmo.person_avatar_url.label = URL avatar
-channel-type.netatmo.person_avatar_url.description = Url de l'avatar de la personne
-
-channel-type.netatmo.person_avatar.label = Avatar
-channel-type.netatmo.person_avatar.description = Avatar de la personne
-
-channel-type.netatmo.person_event.label = Dernière image
-channel-type.netatmo.person_event.description = Image associée au dernier évènement relatif à cette personne
-
-channel-type.netatmo.person_event_url.label = URL de la dernière image
-channel-type.netatmo.person_event_url.description = URL de l'mage associée au dernier évènement relatif à cette personne
-
-# Thing channels
-
-thing-type.netatmo.NAMain.channel.WifiStatus.label = Niveau Wifi
-thing-type.netatmo.NAMain.channel.WifiStatus.description = Indicateur de la qualité de signal Wifi
-
-thing-type.netatmo.NAPlug.channel.WifiStatus.label = Niveau Wifi
-thing-type.netatmo.NAPlug.channel.WifiStatus.description = Indicateur de la qualité de signal Wifi
-
-thing-type.netatmo.NAModule1.channel.RfStatus.label = Niveau radio
-thing-type.netatmo.NAModule1.channel.RfStatus.description = Indicateur de la qualité de signal radio
-thing-type.netatmo.NAModule1.channel.BatteryVP.label = Niveau piles
-thing-type.netatmo.NAModule1.channel.BatteryVP.description = Indicateur du niveau de piles
-thing-type.netatmo.NAModule1.channel.LowBattery.label = Piles faibles
-thing-type.netatmo.NAModule1.channel.LowBattery.description = Indicateur du niveau faible des piles
-
-thing-type.netatmo.NAModule2.channel.RfStatus.label = Niveau radio
-thing-type.netatmo.NAModule2.channel.RfStatus.description = Indicateur de la qualité de signal radio
-thing-type.netatmo.NAModule2.channel.BatteryVP.label = Niveau piles
-thing-type.netatmo.NAModule2.channel.BatteryVP.description = Indicateur du niveau de piles
-thing-type.netatmo.NAModule2.channel.LowBattery.label = Piles faibles
-thing-type.netatmo.NAModule2.channel.LowBattery.description = Indicateur du niveau faible des piles
-
-thing-type.netatmo.NAModule3.channel.RfStatus.label = Niveau radio
-thing-type.netatmo.NAModule3.channel.RfStatus.description = Indicateur de la qualité de signal radio
-thing-type.netatmo.NAModule3.channel.BatteryVP.label = Niveau piles
-thing-type.netatmo.NAModule3.channel.BatteryVP.description = Indicateur du niveau de piles
-thing-type.netatmo.NAModule3.channel.LowBattery.label = Piles faibles
-thing-type.netatmo.NAModule3.channel.LowBattery.description = Indicateur du niveau faible des piles
-
-thing-type.netatmo.NAModule4.channel.RfStatus.label = Niveau radio
-thing-type.netatmo.NAModule4.channel.RfStatus.description = Indicateur de la qualité de signal radio
-thing-type.netatmo.NAModule4.channel.BatteryVP.label = Niveau piles
-thing-type.netatmo.NAModule4.channel.BatteryVP.description = Indicateur du niveau de piles
-thing-type.netatmo.NAModule4.channel.LowBattery.label = Piles faibles
-thing-type.netatmo.NAModule4.channel.LowBattery.description = Indicateur du niveau faible des piles
-
-thing-type.netatmo.NATherm1.channel.RfStatus.label = Niveau radio
-thing-type.netatmo.NATherm1.channel.RfStatus.description = Indicateur de la qualité de signal radio
-thing-type.netatmo.NATherm1.channel.BatteryVP.label = Niveau piles
-thing-type.netatmo.NATherm1.channel.BatteryVP.description = Indicateur du niveau de piles
-thing-type.netatmo.NATherm1.channel.LowBattery.label = Piles faibles
-thing-type.netatmo.NATherm1.channel.LowBattery.description = Indicateur du niveau faible des piles
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/aircare.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/aircare.xml
new file mode 100644 (file)
index 0000000..7d6e5ed
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="netatmo"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="airquality-extended">
+               <label>Air Quality</label>
+               <channels>
+                       <channel id="co2" typeId="co2"/>
+                       <channel id="health-index" typeId="health-index"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="temperature">
+               <label>Temperature</label>
+               <channels>
+                       <channel id="value" typeId="system.indoor-temperature"/>
+                       <channel id="min-today" typeId="min-temp"/>
+                       <channel id="max-today" typeId="max-temp"/>
+                       <channel id="min-time" typeId="timestamp-advanced">
+                               <label>Today Min Timestamp</label>
+                               <description>Moment when temperature was measured at its minimum today.</description>
+                       </channel>
+                       <channel id="max-time" typeId="timestamp-advanced">
+                               <label>Today Max Timestamp</label>
+                               <description>Moment when temperature was measured at its maximum today.</description>
+                       </channel>
+                       <channel id="heat-index" typeId="heat-index"/>
+                       <channel id="dewpoint" typeId="dewpoint"/>
+                       <channel id="dewpoint-depression" typeId="dewpoint-depression"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="pressure">
+               <label>Pressure</label>
+               <channels>
+                       <channel id="value" typeId="system.barometric-pressure"/>
+                       <channel id="absolute" typeId="absolute-pressure"/>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/bridge.xml
deleted file mode 100644 (file)
index b3591df..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
-       <!-- Netatmo API Bridge -->
-       <bridge-type id="netatmoapi">
-               <label>Netatmo API</label>
-               <description>This bridge represents the gateway to Netatmo API.</description>
-               <config-description-ref uri="thing-type:netatmo:bridge"/>
-       </bridge-type>
-
-</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/camera.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/camera.xml
deleted file mode 100644 (file)
index d834b66..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
-       <thing-type id="NACamera">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Welcome Camera</label>
-               <description>This represents a welcome camera at home</description>
-
-               <channels>
-                       <channel id="welcomeCameraStatus" typeId="status"></channel>
-                       <channel id="welcomeCameraSdStatus" typeId="sd_status"></channel>
-                       <channel id="welcomeCameraAlimStatus" typeId="alim_status"></channel>
-                       <channel id="welcomeCameraIsLocal" typeId="is_locale"></channel>
-                       <channel id="welcomeCameraLivePicture" typeId="live_picture"></channel>
-                       <channel id="welcomeCameraLivePictureUrl" typeId="live_picture_url"></channel>
-                       <channel id="welcomeCameraLiveStreamUrl" typeId="live_stream_url"></channel>
-                       <channel id="welcomeHomeEvent" typeId="homeEvent"></channel>
-               </channels>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:camera"/>
-       </thing-type>
-
-       <thing-type id="NOC">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Presence Camera</label>
-               <description>This represents a presence camera at home</description>
-
-               <channels>
-                       <channel id="cameraStatus" typeId="status"></channel>
-                       <channel id="cameraSdStatus" typeId="sd_status"></channel>
-                       <channel id="cameraAlimStatus" typeId="alim_status"></channel>
-                       <channel id="cameraIsLocal" typeId="is_locale"></channel>
-                       <channel id="cameraLivePicture" typeId="live_picture"></channel>
-                       <channel id="cameraLivePictureUrl" typeId="live_picture_url"></channel>
-                       <channel id="cameraLiveStreamUrl" typeId="live_stream_url"></channel>
-                       <channel id="cameraFloodlightAutoMode" typeId="floodlightAutoMode"></channel>
-                       <channel id="cameraFloodlight" typeId="floodlight"></channel>
-               </channels>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:camera"/>
-       </thing-type>
-
-
-       <channel-type id="status">
-               <item-type>Switch</item-type>
-               <label>State</label>
-               <description>State of the camera</description>
-       </channel-type>
-
-       <channel-type id="sd_status">
-               <item-type>Switch</item-type>
-               <label>SD State</label>
-               <description>State of the SD card</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="alim_status">
-               <item-type>Switch</item-type>
-               <label>Alim State</label>
-               <description>State of the power connector</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="is_locale" advanced="true">
-               <item-type>Switch</item-type>
-               <label>Is Local</label>
-               <description>Only for scope access_camera. If Camera and application requesting the information are on the same
-                       network (true/false)</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="live_picture_url" advanced="true">
-               <item-type>String</item-type>
-               <label>Live Snapshot URL</label>
-               <description>Url of the live snapshot for this camera (need scope access_camera)</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="live_picture">
-               <item-type>Image</item-type>
-               <label>Live Snapshot</label>
-               <description>Camera Live Snapshot</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="live_stream_url" advanced="true">
-               <item-type>String</item-type>
-               <label>Live Stream URL</label>
-               <description>Url of the live stream for this camera</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="floodlightAutoMode">
-               <item-type>Switch</item-type>
-               <label>Floodlight Auto-Mode</label>
-               <description>State of the floodlight auto-mode</description>
-       </channel-type>
-
-       <channel-type id="floodlight">
-               <item-type>Switch</item-type>
-               <label>Floodlight</label>
-               <description>State of the floodlight</description>
-       </channel-type>
-
-</thing:thing-descriptions>
index a535a74a26a2fab313181a90b43ec2e07b87e4a5..13c6e55effbcd0be94697d58964d866bc1d01f5c 100644 (file)
        xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
        xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
 
-       <channel-type id="lastStatusStore" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Last Status Store</label>
-               <description>Last Status Store</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="location">
-               <item-type>Location</item-type>
-               <label>Location</label>
-               <description>Location of the device</description>
-               <state readOnly="true" pattern="%2$s°N,%3$s°W, %1$s m"/>
-       </channel-type>
-
-       <channel-type id="temperature">
-               <item-type>Number:Temperature</item-type>
-               <label>Temperature</label>
-               <description>Current temperature</description>
-               <category>Temperature</category>
-               <state readOnly="true" pattern="%.1f %unit%"/>
-       </channel-type>
-
-       <channel-type id="minTemp" advanced="true">
-               <item-type>Number:Temperature</item-type>
-               <label>Min Temp</label>
-               <description>Minimum Temperature on current day</description>
-               <category>Temperature</category>
-               <state readOnly="true" pattern="%.1f %unit%"/>
-       </channel-type>
-
-       <channel-type id="minTempThisWeek" advanced="true">
-               <item-type>Number:Temperature</item-type>
-               <label>Min Temp This Week</label>
-               <description>Minimum Temperature this week</description>
-               <category>Temperature</category>
-               <state readOnly="true" pattern="%.1f %unit%"/>
-       </channel-type>
-
-       <channel-type id="minTempThisMonth" advanced="true">
-               <item-type>Number:Temperature</item-type>
-               <label>Min Temp This Month</label>
-               <description>Minimum Temperature this month</description>
-               <category>Temperature</category>
-               <state readOnly="true" pattern="%.1f %unit%"/>
-       </channel-type>
-
-       <channel-type id="maxTemp" advanced="true">
-               <item-type>Number:Temperature</item-type>
-               <label>Max Temp</label>
-               <description>Maximum Temperature on current day</description>
-               <category>Temperature</category>
-               <state readOnly="true" pattern="%.1f %unit%"/>
-       </channel-type>
-
-       <channel-type id="maxTempThisWeek" advanced="true">
-               <item-type>Number:Temperature</item-type>
-               <label>Max Temp This Week</label>
-               <description>Maximum Temperature this week</description>
-               <category>Temperature</category>
-               <state readOnly="true" pattern="%.1f %unit%"/>
-       </channel-type>
-
-       <channel-type id="maxTempThisMonth" advanced="true">
-               <item-type>Number:Temperature</item-type>
-               <label>Max Temp This Month</label>
-               <description>Maximum Temperature this month</description>
-               <category>Temperature</category>
-               <state readOnly="true" pattern="%.1f %unit%"/>
-       </channel-type>
-
-       <channel-type id="temperatureTrend" advanced="true">
-               <item-type>String</item-type>
-               <label>Temp Trend</label>
-               <description>Temperature Evolution Trend</description>
-               <category>Temperature</category>
-               <state readOnly="true" pattern="%s">
-                       <options>
-                               <option value="up">up</option>
-                               <option value="stable">stable</option>
-                               <option value="down">down</option>
-                       </options>
-               </state>
-       </channel-type>
-
-       <channel-type id="setpointTemp">
-               <item-type>Number:Temperature</item-type>
-               <label>Setpoint</label>
-               <description>Thermostat temperature setpoint</description>
-               <category>Temperature</category>
-               <state pattern="%.1f %unit%" readOnly="false"/>
-       </channel-type>
-
-       <channel-type id="setpointMode">
-               <item-type>String</item-type>
-               <label>Setpoint Mode</label>
-               <description>Chosen setpoint_mode (program, away, hg, manual, off, max)</description>
-               <state readOnly="false">
-                       <options>
-                               <option value="program">Following a weekly schedule</option>
-                               <option value="away">Applying the -away- temperature as defined by the user</option>
-                               <option value="hg">Frost-guard</option>
-                               <option value="manual">Applying a manually set temperature setpoint</option>
-                               <option value="off">Currently off</option>
-                               <option value="max">Heating continuously</option>
-                       </options>
-               </state>
-       </channel-type>
-
-       <channel-type id="ThermRelayCmd" advanced="false">
+       <channel-type id="monitoring-status">
                <item-type>Switch</item-type>
-               <label>Heating Status</label>
-               <description>Indicates whether the furnace is heating or not</description>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="ThermOrientation" advanced="true">
-               <item-type>Number</item-type>
-               <label>Orientation</label>
-               <description>Physical orientation of the thermostat module</description>
-               <state readOnly="true" pattern="%d"/>
-       </channel-type>
-
-       <channel-type id="timeUtc" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Measurement Time</label>
-               <description>Timestamp when data was measured</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+               <label>Monitoring</label>
+               <description>Monitoring state of the camera</description>
        </channel-type>
 
-       <channel-type id="lastPlugSeen" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Last Plug Seen</label>
-               <description>Last Plug Seen</description>
-               <category>Time</category>
+       <channel-type id="window-open">
+               <item-type>Contact</item-type>
+               <label>Window Status</label>
+               <description>Windows of the room are opened.</description>
+               <category>Window</category>
                <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="dateMinCo2" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min CO2</label>
-               <description>Date when minimum CO2 was reached on current day</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMinCo2ThisWeek" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min CO2 This Week</label>
-               <description>Date when minimum CO2 was reached this week</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMinCo2ThisMonth" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min CO2 This Month</label>
-               <description>Date when minimum CO2 was reached this month</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMaxCo2" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max CO2</label>
-               <description>Date when maximum CO2 was reached on current day</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMaxCo2ThisWeek" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max CO2 This Week</label>
-               <description>Date when maximum CO2 was reached this week</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMaxCo2ThisMonth" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max CO2 This Month</label>
-               <description>Date when maximum CO2 was reached this month</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMinTemp" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Temp</label>
-               <description>Date when minimum temperature was reached on current day</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMinTempThisWeek" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Temp This Week</label>
-               <description>Date when minimum temperature was reached this week</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMinTempThisMonth" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Temp This Month</label>
-               <description>Date when minimum temperature was reached this month</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMaxTemp" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Temp</label>
-               <description>Date when maximum temperature was reached on current day</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMaxTempThisWeek" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Temp This Week</label>
-               <description>Date when maximum temperature was reached this week</description>
-               <category>Time</category>
-               <state readOnly="true"/>
-       </channel-type>
-
-       <channel-type id="dateMaxTempThisMonth" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Temp This Month</label>
-               <description>Date when maximum temperature was reached this month</description>
-               <category>Time</category>
+       <channel-type id="anticipating-heating">
+               <item-type>Switch</item-type>
+               <label>Anticipated Heating</label>
+               <description>Anticipates next scheduled setpoint.</description>
                <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="dateMinHumidity" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Humidity</label>
-               <description>Date when minimum humidity was reached on current day</description>
-               <category>Time</category>
+       <channel-type id="heating-status">
+               <item-type>Contact</item-type>
+               <label>Heating Status</label>
+               <description>Is the furnace currently heating?</description>
                <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="dateMinHumidityThisWeek" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Humidity This Week</label>
-               <description>Date when minimum humidity was reached this week</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="camera-event">
+               <kind>trigger</kind>
+               <label>Camera Event</label>
+               <event>
+                       <options>
+                               <option value="ANIMAL"/>
+                               <option value="HUMAN"/>
+                               <option value="MOVEMENT"/>
+                               <option value="VEHICLE"/>
+                       </options>
+               </event>
        </channel-type>
 
-       <channel-type id="dateMinHumidityThisMonth" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Humidity This Month</label>
-               <description>Date when minimum humidity was reached this month</description>
-               <category>Time</category>
+       <channel-type id="battery-status">
+               <item-type>String</item-type>
+               <label>Battery Status</label>
+               <description>Description of the battery status.</description>
                <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="dateMaxHumidity" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Humidity</label>
-               <description>Date when maximum humidity was reached on current day</description>
-               <category>Time</category>
+       <channel-type id="video-url">
+               <item-type>String</item-type>
+               <label>Video URL</label>
+               <description>URL of the event recording.</description>
                <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="dateMaxHumidityThisWeek" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Humidity This Week</label>
-               <description>Date when maximum humidity was reached this week</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="planning">
+               <item-type>String</item-type>
+               <label>Planning</label>
+               <description>Planning currently applied when following weekly schedule.</description>
+               <state readOnly="false" pattern="%s"/>
        </channel-type>
 
-       <channel-type id="dateMaxHumidityThisMonth" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Humidity This Month</label>
-               <description>Date when maximum humidity was reached this month</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="person-count">
+               <item-type>Number</item-type>
+               <label>Person Count</label>
+               <description>Total number of persons that are at home.</description>
+               <tags>
+                       <tag>Status</tag>
+                       <tag>Presence</tag>
+               </tags>
+               <state readOnly="true" pattern="%d"/>
        </channel-type>
 
-       <channel-type id="dateMinNoise" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Noise</label>
-               <description>Date when minimum noise was reached on current day</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="unknown-person-count">
+               <item-type>Number</item-type>
+               <label>Unknown Persons Count</label>
+               <description>Total number of unknown persons that are at home.</description>
+               <state readOnly="true" pattern="%d"/>
        </channel-type>
 
-       <channel-type id="dateMinNoiseThisWeek" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Noise This Week</label>
-               <description>Date when minimum noise was reached this week</description>
-               <category>Time</category>
+       <channel-type id="unknown-person-picture">
+               <item-type>Image</item-type>
+               <label>Unknown Person Snapshot</label>
+               <description>Snapshot of unknown person that is at home.</description>
                <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="dateMinNoiseThisMonth" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Noise This Month</label>
-               <description>Date when minimum noise was reached this month</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="rain-quantity">
+               <item-type>Number:Length</item-type>
+               <label>Rain Quantity</label>
+               <description>Quantity of water over the period.</description>
+               <category>Rain</category>
+               <state readOnly="true" pattern="%.1f %unit%"/>
        </channel-type>
 
-       <channel-type id="dateMaxNoise" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Noise</label>
-               <description>Date when maximum noise was reached on current day</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="rain-intensity">
+               <item-type>Number:Speed</item-type>
+               <label>Rain Intensity</label>
+               <description>Current precipitation intensity.</description>
+               <category>Rain</category>
+               <state readOnly="true" pattern="%.1f mm/h"/>
        </channel-type>
 
-       <channel-type id="dateMaxNoiseThisWeek" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Noise This Week</label>
-               <description>Date when maximum noise was reached this week</description>
+       <channel-type id="setpoint-duration">
+               <item-type>Number:Time</item-type>
+               <label>Setpoint Duration</label>
+               <description>Default duration of manual setpoint changes.</description>
                <category>Time</category>
-               <state readOnly="true"/>
+               <state pattern="%d %unit%" min="5" max="720" step="5"/>
        </channel-type>
 
-       <channel-type id="dateMaxNoiseThisMonth" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Noise This Month</label>
-               <description>Date when maximum noise was reached this month</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="floodlight-mode">
+               <item-type>String</item-type>
+               <label>Floodlight</label>
+               <description>State of the floodlight (On/Off/Auto)</description>
+               <state pattern="%s">
+                       <options>
+                               <option value="ON">On</option>
+                               <option value="OFF">Off</option>
+                               <option value="AUTO">Auto</option>
+                       </options>
+               </state>
        </channel-type>
 
-       <channel-type id="dateMinPressure" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Pressure</label>
-               <description>Date when minimum pressure was reached on current day</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="trend" advanced="true">
+               <item-type>String</item-type>
+               <label>Trend</label>
+               <description>Evolution of the measure over time.</description>
+               <category>Line</category>
+               <state readOnly="true" pattern="%s">
+                       <options>
+                               <option value="UP">Up</option>
+                               <option value="STABLE">Stable</option>
+                               <option value="DOWN">Down</option>
+                       </options>
+               </state>
        </channel-type>
 
-       <channel-type id="dateMinPressureThisWeek" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Pressure This Week</label>
-               <description>Date when minimum pressure was reached this week</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="sd-card-status">
+               <item-type>String</item-type>
+               <label>SD Card State</label>
+               <description>State of the SD card</description>
+               <state readOnly="true" pattern="%s">
+                       <options>
+                               <option value="SD_CARD_MISSING">Missing SD Card</option>
+                               <option value="SD_CARD_INSERTED">SD Card inserted</option>
+                               <option value="SD_CARD_FORMATTED">SD Card formated</option>
+                               <option value="SD_CARD_WORKING">Working SD Card</option>
+                               <option value="SD_CARD_DEFECTIVE">Defective SD Card</option>
+                               <option value="SD_CARD_INCOMPATIBLE_SPEED">Incompatible SD Card speed</option>
+                               <option value="SD_CARD_INSUFFICIENT_SPACE">Insufficient SD Card space</option>
+                       </options>
+               </state>
        </channel-type>
 
-       <channel-type id="dateMinPressureThisMonth" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Min Pressure This Month</label>
-               <description>Date when minimum pressure was reached this month</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="alim-status">
+               <item-type>String</item-type>
+               <label>Alim State</label>
+               <description>State of the power connector</description>
+               <state readOnly="true" pattern="%s">
+                       <options>
+                               <option value="ALIM_INCORRECT_POWER">Incorrect power adapter</option>
+                               <option value="ALIM_CORRECT_POWER">Correct power adapter</option>
+                       </options>
+               </state>
        </channel-type>
 
-       <channel-type id="dateMaxPressure" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Pressure</label>
-               <description>Date when maximum pressure was reached on current day</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="video-status">
+               <item-type>String</item-type>
+               <label>Video Status</label>
+               <description>Status of the video (recording, deleted or available).</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="RECORDING">Recording</option>
+                               <option value="DELETED">Deleted</option>
+                               <option value="AVAILABLE">Available</option>
+                       </options>
+               </state>
        </channel-type>
 
-       <channel-type id="dateMaxPressureThisWeek" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Pressure This Week</label>
-               <description>Date when maximum pressure was reached this week</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="setpoint">
+               <item-type>Number:Temperature</item-type>
+               <label>Setpoint</label>
+               <description>Thermostat temperature setpoint.</description>
+               <category>Temperature</category>
+               <tags>
+                       <tag>Setpoint</tag>
+                       <tag>Temperature</tag>
+               </tags>
+               <state pattern="%.1f %unit%" readOnly="false" min="7" max="30" step="0.5"/>
        </channel-type>
 
-       <channel-type id="dateMaxPressureThisMonth" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Pressure This Month</label>
-               <description>Date when maximum pressure was reached this month</description>
-               <category>Time</category>
-               <state readOnly="true"/>
+       <channel-type id="th-mode">
+               <item-type>String</item-type>
+               <label>Thermostat Mode</label>
+               <description>Chosen thermostat mode (home, frost guard, manual, max).</description>
+               <state readOnly="false">
+                       <options>
+                               <option value="HOME">Home</option>
+                               <option value="FROST_GUARD">Frost Guard</option>
+                               <option value="MANUAL">Manual</option>
+                               <option value="MAX">Max</option>
+                       </options>
+               </state>
        </channel-type>
 
-       <channel-type id="lastBilan" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Available Bilan</label>
-               <description>Month of the last available thermostat bilan</description>
-               <category>Time</category>
-               <state readOnly="true" pattern="%1$td.%1$tm.%1$tY"/>
+       <channel-type id="energy-mode">
+               <item-type>String</item-type>
+               <label>House Mode</label>
+               <description>Chosen mode for the house (schedule, away, frost guard, manual).</description>
+               <state readOnly="false">
+                       <options>
+                               <option value="SCHEDULE">Following a weekly schedule</option>
+                               <option value="AWAY">Applying the -away- temperature as defined by the user</option>
+                               <option value="FROST_GUARD">Frost-guard</option>
+                               <option value="MANUAL">Applying a manually set temperature setpoint</option>
+                       </options>
+               </state>
        </channel-type>
 
-       <channel-type id="connectedBoiler" advanced="true">
-               <item-type>Switch</item-type>
-               <label>Plug Connected Boiler</label>
-               <state readOnly="true"/>
+       <channel-type id="room-heating-percent">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Heating Power</label>
+               <description>Percentage of heating power.</description>
+               <category>Energy</category>
+               <state readOnly="true" pattern="%.0f %unit%"/>
        </channel-type>
 
-       <channel-type id="lastMessage" advanced="true">
+       <channel-type id="timestamp">
                <item-type>DateTime</item-type>
-               <label>Last Message</label>
-               <description>Last Message emitted by the module</description>
+               <label>Timestamp</label>
+               <description>Moment when data was measured.</description>
                <category>Time</category>
                <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="setpointEndTime" advanced="true">
+       <channel-type id="timestamp-advanced" advanced="true">
                <item-type>DateTime</item-type>
-               <label>Setpoint End</label>
-               <description>Thermostat goes back to schedule after that timestamp.</description>
-               <category>Time</category>
-               <state readOnly="true" pattern="%1$td.%1$tm.%1$tY %1$tH:%1$tM"/>
-       </channel-type>
-
-       <channel-type id="lastThermSeen" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Last Therm Seen</label>
-               <description>Last Them Seen</description>
+               <label>Timestamp</label>
+               <description>Moment when data was measured.</description>
                <category>Time</category>
                <state readOnly="true"/>
        </channel-type>
        <channel-type id="co2">
                <item-type>Number:Dimensionless</item-type>
                <label>CO2</label>
-               <description>Air Quality</description>
-               <category>Carbondioxide</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="minCo2" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Min Co2</label>
-               <description>Minimum CO2 on current day</description>
-               <category>Carbondioxide</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="minCo2ThisWeek" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Min Co2 This Week</label>
-               <description>Minimum CO2 this week</description>
-               <category>Carbondioxide</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="minCo2ThisMonth" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Min Co2 This Month</label>
-               <description>Minimum CO2 this month</description>
-               <category>Carbondioxide</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="maxCo2" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Max Co2</label>
-               <description>Maximum CO2 on current day</description>
-               <category>Carbondioxide</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="maxCo2ThisWeek" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Max Co2 This Week</label>
-               <description>Maximum CO2 this week</description>
-               <category>Carbondioxide</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="maxCo2ThisMonth" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Max Co2 This Month</label>
-               <description>Maximum CO2 this month</description>
+               <description>Air Quality indicator.</description>
                <category>Carbondioxide</category>
+               <tags>
+                       <tag>Measurement</tag>
+                       <tag>CO2</tag>
+               </tags>
                <state readOnly="true" pattern="%d %unit%"/>
        </channel-type>
 
        <channel-type id="noise">
                <item-type>Number:Dimensionless</item-type>
                <label>Noise</label>
-               <description>Current Noise Level</description>
-               <category>Noise</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="minNoise" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Min Noise</label>
-               <description>Minimum Noise on current day</description>
-               <category>Noise</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="minNoiseThisWeek" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Min Noise This Week</label>
-               <description>Minimum Noise this week</description>
-               <category>Noise</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="minNoiseThisMonth" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Min Noise This Month</label>
-               <description>Minimum Noise this month</description>
-               <category>Noise</category>
+               <description>Current Noise Level.</description>
+               <category>SoundVolume</category>
+               <tags>
+                       <tag>Measurement</tag>
+                       <tag>Noise</tag>
+               </tags>
                <state readOnly="true" pattern="%d %unit%"/>
        </channel-type>
 
-       <channel-type id="maxNoise" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Max Noise</label>
-               <description>Maximum Noise on current day</description>
-               <category>Noise</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="maxNoiseThisWeek" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Max Noise This Week</label>
-               <description>Maximum Noise this week</description>
-               <category>Noise</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="maxNoiseThisMonth" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Max Noise This Month</label>
-               <description>Maximum Noise this month</description>
-               <category>Noise</category>
-               <state readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="healthindex">
-               <item-type>String</item-type>
+       <channel-type id="health-index">
+               <item-type>Number</item-type>
                <label>Health Index</label>
-               <description>Health Index (healthy, fine, fair, poor, unhealthy)</description>
-               <state readOnly="true" pattern="%s">
+               <description>Health index (healthy, fine, fair, poor, unhealthy).</description>
+               <state readOnly="true" pattern="%d">
                        <options>
-                               <option value="healthy">healthy</option>
-                               <option value="fine">fine</option>
-                               <option value="fair">fair</option>
-                               <option value="poor">poor</option>
-                               <option value="unhealthy">unhealthy</option>
+                               <option value="0">Healthy</option>
+                               <option value="1">Fine</option>
+                               <option value="2">Fair</option>
+                               <option value="3">Poor</option>
+                               <option value="4">Unhealthy</option>
                        </options>
                </state>
        </channel-type>
 
-       <channel-type id="pressure">
-               <item-type>Number:Pressure</item-type>
-               <label>Pressure</label>
-               <description>Current pressure</description>
-               <category>Pressure</category>
-               <state readOnly="true" pattern="%.3f %unit%"/>
-       </channel-type>
-
-       <channel-type id="minPressure" advanced="true">
-               <item-type>Number:Pressure</item-type>
-               <label>Min Pressure</label>
-               <description>Minimum Pressure on current day</description>
-               <category>Pressure</category>
-               <state readOnly="true" pattern="%.3f %unit%"/>
-       </channel-type>
-
-       <channel-type id="minPressureThisWeek" advanced="true">
-               <item-type>Number:Pressure</item-type>
-               <label>Min Pressure This Week</label>
-               <description>Minimum Pressure this week</description>
-               <category>Pressure</category>
-               <state readOnly="true" pattern="%.3f %unit%"/>
-       </channel-type>
-
-       <channel-type id="minPressureThisMonth" advanced="true">
-               <item-type>Number:Pressure</item-type>
-               <label>Min Pressure This Month</label>
-               <description>Minimum Pressure this month</description>
-               <category>Pressure</category>
-               <state readOnly="true" pattern="%.3f %unit%"/>
-       </channel-type>
-
-       <channel-type id="maxPressure" advanced="true">
-               <item-type>Number:Pressure</item-type>
-               <label>Max Pressure</label>
-               <description>Maximum Pressure on current day</description>
-               <category>Pressure</category>
-               <state readOnly="true" pattern="%.3f %unit%"/>
+       <channel-type id="humidex" advanced="true">
+               <item-type>Number</item-type>
+               <label>Humidex</label>
+               <description>Computed Humidex: felt temperature.</description>
+               <state readOnly="true" pattern="%.0f"/>
        </channel-type>
 
-       <channel-type id="maxPressureThisWeek" advanced="true">
-               <item-type>Number:Pressure</item-type>
-               <label>Max Pressure This Week</label>
-               <description>Maximum Pressure this week</description>
-               <category>Pressure</category>
-               <state readOnly="true" pattern="%.3f %unit%"/>
+       <channel-type id="humidex-scale" advanced="true">
+               <item-type>Number</item-type>
+               <label>Humidex Appreciation</label>
+               <description>Appreciation of the Humidex.</description>
+               <state readOnly="true" pattern="%d">
+                       <options>
+                               <option value="0">Comfortable</option>
+                               <option value="1">Some discomfort</option>
+                               <option value="2">Great discomfort</option>
+                               <option value="3">Dangerous</option>
+                               <option value="4">Very dangerous</option>
+                       </options>
+               </state>
        </channel-type>
 
-       <channel-type id="maxPressureThisMonth" advanced="true">
-               <item-type>Number:Pressure</item-type>
-               <label>Max Pressure This Month</label>
-               <description>Maximum Pressure this month</description>
-               <category>Pressure</category>
-               <state readOnly="true" pattern="%.3f %unit%"/>
+       <channel-type id="event-type">
+               <item-type>String</item-type>
+               <label>Event Type</label>
+               <description>Description of the event.</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="PERSON">Face detected</option>
+                               <option value="PERSON_AWAY">Person has left home</option>
+                               <option value="PERSON_HOME">Person is at home</option>
+                               <option value="OUTDOOR">Motion detected by Presence</option>
+                               <option value="MOVEMENT">Motion detected</option>
+                               <option value="HUMAN">Human seen</option>
+                               <option value="ANIMAL">Animal seen</option>
+                               <option value="NEW_MODULE">New Module has been paired</option>
+                               <option value="MODULE_CONNECT">Module is connected with the Indoor Camera</option>
+                               <option value="MODULE_DISCONNECT">Module lost its connection with the Indoor Camera</option>
+                               <option value="MODULE_LOW_BATTERY">Module's battery is low</option>
+                               <option value="MODULE_END_UPDATE">Module's firmware update is over</option>
+                               <option value="CONNECTION">Camera connected to Netatmo</option>
+                               <option value="DISCONNECTION">Camera disconnected from Netatmo</option>
+                               <option value="ON">Monitoring activated</option>
+                               <option value="OFF">Monitoring stopped</option>
+                               <option value="BOOT">Camera booting</option>
+                               <option value="SD">SD card status changed</option>
+                               <option value="ALIM">Power status changed</option>
+                       </options>
+               </state>
        </channel-type>
 
-       <channel-type id="pressureTrend" advanced="true">
+       <channel-type id="event-subtype">
                <item-type>String</item-type>
-               <label>Pressure Trend</label>
-               <description>Pressure evolution trend for last 12h (up, down, stable)</description>
-               <category>Pressure</category>
-               <state readOnly="true" pattern="%s">
+               <label>Event Sub Type</label>
+               <description>Details of the event.</description>
+               <state readOnly="true">
                        <options>
-                               <option value="up">up</option>
-                               <option value="stable">stable</option>
-                               <option value="down">down</option>
+                               <option value="SD_CARD_MISSING">Missing SD Card</option>
+                               <option value="SD_CARD_INSERTED">SD Card inserted</option>
+                               <option value="SD_CARD_FORMATTED">SD Card formated</option>
+                               <option value="SD_CARD_WORKING">Working SD Card</option>
+                               <option value="SD_CARD_DEFECTIVE">Defective SD Card</option>
+                               <option value="SD_CARD_INCOMPATIBLE_SPEED">Incompatible SD Card speed</option>
+                               <option value="SD_CARD_INSUFFICIENT_SPACE">Insufficient SD Card space</option>
+                               <option value="ALIM_INCORRECT_POWER">Incorrect power adapter</option>
+                               <option value="ALIM_CORRECT_POWER">Correct power adapter</option>
+                               <option value="PERSON_ARRIVAL">Person arrived</option>
+                               <option value="PERSON_DEPARTURE">Person has left</option>
+                               <option value="PERSON_SEEN">Person has been seen</option>
+                               <option value="MOVEMENT_HUMAN">Human seen</option>
+                               <option value="MOVEMENT_VEHICLE">Car seen</option>
+                               <option value="MOVEMENT_ANIMAL">Animal seen</option>
                        </options>
                </state>
        </channel-type>
 
-       <channel-type id="planning" advanced="false">
+       <channel-type id="home-event">
+               <kind>trigger</kind>
+               <label>Home Event</label>
+               <event>
+                       <options>
+                               <option value="PERSON"/>
+                               <option value="PERSON_AWAY"/>
+                               <option value="PERSON_HOME"/>
+                               <option value="OUTDOOR"/>
+                               <option value="MOVEMENT"/>
+                               <option value="HUMAN"/>
+                               <option value="ANIMAL"/>
+                               <option value="NEW_MODULE"/>
+                               <option value="MODULE_CONNECT"/>
+                               <option value="MODULE_DISCONNECT"/>
+                               <option value="MODULE_LOW_BATTERY"/>
+                               <option value="MODULE_END_UPDATE"/>
+                               <option value="CONNECTION"/>
+                               <option value="DISCONNECTION"/>
+                               <option value="ON"/>
+                               <option value="OFF"/>
+                               <option value="BOOT"/>
+                               <option value="SD"/>
+                               <option value="ALIM"/>
+                       </options>
+               </event>
+       </channel-type>
+
+       <channel-type id="message">
                <item-type>String</item-type>
-               <label>Planning</label>
-               <description>Heat planning currently used</description>
-               <state pattern="%s"/>
+               <label>Message</label>
+               <description>Message sent by Netatmo corresponding to given event.</description>
+               <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="absolutePressure" advanced="true">
-               <item-type>Number:Pressure</item-type>
-               <label>Abs Pressure</label>
-               <description>Absolute pressure</description>
-               <category>Pressure</category>
-               <state readOnly="true" pattern="%.3f %unit%"/>
+       <channel-type id="event-picture">
+               <item-type>Image</item-type>
+               <label>Event Snapshot</label>
+               <description>Capture image of the event.</description>
+               <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="humidity">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Humidity</label>
-               <description>Current humidity</description>
-               <category>Humidity</category>
-               <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+       <channel-type id="event-picture-url" advanced="true">
+               <item-type>String</item-type>
+               <label>Event Snapshot URL</label>
+               <description>Url of the event snapshot.</description>
+               <state readOnly="true"></state>
        </channel-type>
 
-       <channel-type id="minHumidity" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Min Humidity</label>
-               <description>Minimum Humidity on current day</description>
-               <category>Humidity</category>
-               <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+       <channel-type id="person-id">
+               <item-type>String</item-type>
+               <label>Person ID</label>
+               <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="minHumidityThisWeek" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Min Humidity This Week</label>
-               <description>Minimum Humidity this week</description>
-               <category>Humidity</category>
-               <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+       <channel-type id="camera-id">
+               <item-type>String</item-type>
+               <label>Camera ID</label>
+               <description>ID of the camera that triggered the event.</description>
+               <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="minHumidityThisMonth" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Min Humidity This Month</label>
-               <description>Minimum Humidity this month</description>
-               <category>Humidity</category>
-               <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+       <channel-type id="avatar-picture-url">
+               <item-type>String</item-type>
+               <label>Avatar Picture URL</label>
+               <description>URL for the avatar of this person.</description>
+               <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="maxHumidity" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Max Humidity</label>
-               <description>Maximum Humidity on current day</description>
-               <category>Humidity</category>
-               <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+       <channel-type id="avatar-picture">
+               <item-type>Image</item-type>
+               <label>Avatar Picture</label>
+               <description>Avatar of this person.</description>
+               <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="maxHumidityThisWeek" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Max Humidity This Week</label>
-               <description>Minimum Humidity this week</description>
-               <category>Humidity</category>
-               <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+       <channel-type id="at-home">
+               <item-type>Switch</item-type>
+               <label>At Home</label>
+               <description>Indicates if this person is known to be at home or not.</description>
        </channel-type>
 
-       <channel-type id="maxHumidityThisMonth" advanced="true">
-               <item-type>Number:Dimensionless</item-type>
-               <label>Max Humidity This Month</label>
-               <description>Maximum Humidity this month</description>
-               <category>Humidity</category>
-               <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+       <channel-type id="rssi" advanced="true">
+               <item-type>Number:Power</item-type>
+               <label>Signal</label>
+               <description>Signal strength indicator.</description>
+               <category>QualityOfService</category>
+               <state readOnly="true" pattern="%d %unit%"></state>
        </channel-type>
 
-       <channel-type id="humidex">
-               <item-type>Number</item-type>
-               <label>Humidex</label>
-               <description>Computed Humidex index</description>
-               <category>Temperature</category>
-               <state readOnly="true" pattern="%.0f"/>
+       <channel-type id="absolute-pressure" advanced="true">
+               <item-type>Number:Pressure</item-type>
+               <label>Absolute Pressure</label>
+               <description>Pressure measured relative to a full vacuum.</description>
+               <category>Pressure</category>
+               <tags>
+                       <tag>Measurement</tag>
+                       <tag>Pressure</tag>
+               </tags>
+               <state readOnly="true" pattern="%.0f %unit%"/>
        </channel-type>
 
-       <channel-type id="heatIndex">
+       <channel-type id="heat-index">
                <item-type>Number:Temperature</item-type>
                <label>Heat Index</label>
-               <description>Computed Heat Index</description>
+               <description>Apparent computed temperature (based on temperature and humidity).</description>
                <category>Temperature</category>
                <state readOnly="true" pattern="%.0f %unit%"/>
        </channel-type>
 
-       <channel-type id="dewPoint" advanced="true">
+       <channel-type id="dewpoint" advanced="true">
                <item-type>Number:Temperature</item-type>
                <label>Dewpoint</label>
-               <description>Computed Dewpoint Temperature</description>
+               <description>Temperature to which air must be cooled to become saturated with water vapor.</description>
                <category>Temperature</category>
                <state readOnly="true" pattern="%.1f %unit%"/>
        </channel-type>
 
-       <channel-type id="dewPointDepression" advanced="true">
+       <channel-type id="dewpoint-depression" advanced="true">
                <item-type>Number:Temperature</item-type>
                <label>Dewpoint Depression</label>
-               <description>Computed Dewpoint Depression</description>
+               <description>Difference between the temperature and the dewpoint.</description>
                <state readOnly="true" pattern="%.1f %unit%"/>
        </channel-type>
 
-       <channel-type id="rain">
-               <item-type>Number:Length</item-type>
-               <label>Rain</label>
-               <description>Quantity of water</description>
-               <category>Rain</category>
-               <state readOnly="true" pattern="%.2f %unit%"/>
-       </channel-type>
-
-       <channel-type id="rain1" advanced="true">
-               <item-type>Number:Length</item-type>
-               <label>Rain 1h</label>
-               <description>Quantity of water on last hour</description>
-               <category>Rain</category>
-               <state readOnly="true" pattern="%.2f %unit%"/>
-       </channel-type>
-
-       <channel-type id="rain24" advanced="true">
-               <item-type>Number:Length</item-type>
-               <label>Rain 24h</label>
-               <description>Quantity of water on last day</description>
-               <category>Rain</category>
-               <state readOnly="true" pattern="%.2f %unit%"/>
-       </channel-type>
-
-       <channel-type id="rainThisWeek" advanced="true">
-               <item-type>Number:Length</item-type>
-               <label>Rain This Week</label>
-               <description>Quantity of water this week</description>
-               <category>Rain</category>
-               <state readOnly="true" pattern="%.2f %unit%"/>
+       <channel-type id="max-temp" advanced="true">
+               <item-type>Number:Temperature</item-type>
+               <label>Max Temp</label>
+               <description>Maximum Temperature on current day.</description>
+               <category>Temperature</category>
+               <state readOnly="true" pattern="%.1f %unit%"/>
        </channel-type>
 
-       <channel-type id="rainThisMonth" advanced="true">
-               <item-type>Number:Length</item-type>
-               <label>Rain This Month</label>
-               <description>Quantity of water this month</description>
-               <category>Rain</category>
-               <state readOnly="true" pattern="%.2f %unit%"/>
+       <channel-type id="min-temp" advanced="true">
+               <item-type>Number:Temperature</item-type>
+               <label>Min Temp</label>
+               <description>Minimum Temperature on current day</description>
+               <category>Temperature</category>
+               <state readOnly="true" pattern="%.1f %unit%"/>
        </channel-type>
 
-       <channel-type id="WindAngle">
+       <channel-type id="gust-angle">
                <item-type>Number:Angle</item-type>
-               <label>Wind Angle</label>
-               <description>Current 5 minutes average wind direction</description>
+               <label>Gust Angle</label>
+               <description>Direction of the last 5 minutes highest gust wind</description>
                <category>Wind</category>
                <state min="0" max="360" step="1" readOnly="true" pattern="%d %unit%"/>
        </channel-type>
 
-       <channel-type id="WindStrength">
+       <channel-type id="gust-strength">
                <item-type>Number:Speed</item-type>
-               <label>Wind Strength</label>
-               <description>Current 5 minutes average wind speed</description>
+               <label>Gust Strength</label>
+               <description>Speed of the last 5 minutes highest gust wind</description>
                <category>Wind</category>
                <state readOnly="true" pattern="%.1f %unit%"/>
        </channel-type>
 
-       <channel-type id="MaxWindStrength" advanced="true">
+       <channel-type id="max-wind-strength" advanced="true">
                <item-type>Number:Speed</item-type>
                <label>Max Wind Strength</label>
                <description>Maximum wind strength recorded</description>
                <state readOnly="true" pattern="%.1f %unit%"/>
        </channel-type>
 
-       <channel-type id="DateMaxWindStrength" advanced="true">
-               <item-type>DateTime</item-type>
-               <label>Date Max Wind Strength</label>
-               <description>Timestamp when MaxWindStrength was recorded.</description>
-               <category>Time</category>
-               <state readOnly="true" pattern="%1$td.%1$tm.%1$tY %1$tH:%1$tM"/>
-       </channel-type>
-
-       <channel-type id="GustAngle">
-               <item-type>Number:Angle</item-type>
-               <label>Gust Angle</label>
-               <description>Direction of the last 5 minutes highest gust wind</description>
-               <category>Wind</category>
-               <state min="0" max="360" step="1" readOnly="true" pattern="%d %unit%"/>
-       </channel-type>
-
-       <channel-type id="GustStrength">
-               <item-type>Number:Speed</item-type>
-               <label>Gust Strength</label>
-               <description>Speed of the last 5 minutes highest gust wind</description>
-               <category>Wind</category>
-               <state readOnly="true" pattern="%.1f %unit%"/>
+       <channel-type id="location">
+               <item-type>Location</item-type>
+               <label>Location</label>
+               <description>Location of the device</description>
+               <state readOnly="true" pattern="%2$s°N,%3$s°W, %1$s m"/>
        </channel-type>
 
-       <channel-type id="homeEvent">
-               <kind>trigger</kind>
-               <label>Home Event</label>
-               <description>Home event</description>
-               <event>
-                       <options>
-                               <option value="PERSON">person</option>
-                               <option value="PERSON_AWAY">person_away</option>
-                               <option value="MOVEMENT">movement</option>
-                               <option value="CONNECTION">connection</option>
-                               <option value="DISCONNECTION">disconnection</option>
-                               <option value="ON">on</option>
-                               <option value="OFF">off</option>
-                               <option value="BOOT">boot</option>
-                               <option value="SD">sd</option>
-                               <option value="ALIM">alim</option>
-                               <option value="NEW_MODULE">new_module</option>
-                               <option value="MODULE_CONNECT">module_connect</option>
-                               <option value="MODULE_DISCONNECT">module_disconnect</option>
-                               <option value="MODULE_LOW_BATTERY">module_low_battery</option>
-                               <option value="MODULE_END_UPDATE">module_end_update</option>
-                               <option value="TAG_BIG_MOVE">tag_big_move</option>
-                               <option value="TAG_SMALL_MOVE">tag_small_move</option>
-                               <option value="TAG_UNINSTALLED">tag_uninstalled</option>
-                               <option value="TAG_OPEN">tag_open</option>
-                       </options>
-               </event>
+       <channel-type id="live-stream-url">
+               <item-type>String</item-type>
+               <label>Live Stream URL</label>
+               <description>URL of the live stream for this camera.</description>
+               <state readOnly="true"></state>
+               <config-description>
+                       <parameter name="quality" type="text">
+                               <label>Quality Level</label>
+                               <description>Defines quality level of the feed (the higher the more bandwidth)</description>
+                               <options>
+                                       <option value="low">Low Quality</option>
+                                       <option value="poor">Poor Quality</option>
+                                       <option value="high">High Quality</option>
+                               </options>
+                               <default>poor</default>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="live-picture">
+               <item-type>Image</item-type>
+               <label>Live Snapshot</label>
+               <description>Camera Live Snapshot.</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="live-picture-url" advanced="true">
+               <item-type>String</item-type>
+               <label>Live Snapshot URL</label>
+               <description>URL of the live snapshot for this camera (need scope access_camera).</description>
+               <state readOnly="true"></state>
        </channel-type>
 
 </thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/common.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/common.xml
new file mode 100644 (file)
index 0000000..1fd7424
--- /dev/null
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="netatmo"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="signal">
+               <label>Signal</label>
+               <channels>
+                       <channel id="value" typeId="rssi"/>
+                       <channel id="strength" typeId="system.signal-strength"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="battery">
+               <label>Battery</label>
+               <channels>
+                       <channel id="value" typeId="system.battery-level"/>
+                       <channel id="low-battery" typeId="system.low-battery"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="location">
+               <label>Location</label>
+               <channels>
+                       <channel id="value" typeId="location"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="battery-extended">
+               <label>Battery</label>
+               <channels>
+                       <channel id="value" typeId="system.battery-level"/>
+                       <channel id="status" typeId="battery-status"/>
+                       <channel id="low-battery" typeId="system.low-battery"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="timestamp">
+               <label>Timestamp</label>
+               <channels>
+                       <channel id="last-seen" typeId="timestamp">
+                               <label>Last Seen</label>
+                               <description>Last time the module reported its presence.</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="timestamp-extended">
+               <label>Timestamp</label>
+               <channels>
+                       <channel id="measures" typeId="timestamp">
+                               <label>Measures Timestamp</label>
+                               <description>Moment of the last measures update.</description>
+                       </channel>
+                       <channel id="last-seen" typeId="timestamp-advanced">
+                               <label>Last Seen</label>
+                               <description>Last time the module reported its presence.</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/energy.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/energy.xml
new file mode 100644 (file)
index 0000000..8a58707
--- /dev/null
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="netatmo"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="plug">
+               <label>Thermostat Plug</label>
+               <channels>
+                       <channel id="boiler-status" typeId="boiler-status"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="room-temperature">
+               <label>Room Temperature</label>
+               <channels>
+                       <channel id="value" typeId="system.indoor-temperature"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="room-properties">
+               <label>Room Status</label>
+               <channels>
+                       <channel id="window-open" typeId="window-open"/>
+                       <channel id="anticipating" typeId="anticipating-heating"/>
+                       <channel id="heating-power-request" typeId="room-heating-percent"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="setpoint">
+               <label>Setpoint</label>
+               <channels>
+                       <channel id="value" typeId="setpoint"/>
+                       <channel id="mode" typeId="th-mode"/>
+                       <channel id="start" typeId="timestamp">
+                               <label>Setpoint Start</label>
+                               <description>Start time of the currently applied setpoint.</description>
+                       </channel>
+                       <channel id="end" typeId="timestamp">
+                               <label>Setpoint End</label>
+                               <description>End time of the currently applied setpoint.</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="th-properties">
+               <label>Thermostat</label>
+               <channels>
+                       <channel id="relay-status" typeId="heating-status"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="energy">
+               <label>Home Energy</label>
+               <channels>
+                       <channel id="setpoint-duration" typeId="setpoint-duration"/>
+                       <channel id="planning" typeId="planning"/>
+                       <channel id="mode" typeId="energy-mode"/>
+                       <channel id="end" typeId="timestamp">
+                               <label>Mode End</label>
+                               <description>End time of the currently applied setpoint.</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/healthyhomecoach.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/healthyhomecoach.xml
deleted file mode 100644 (file)
index 1d4a4ed..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
-       <thing-type id="NHC">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Healthy Home Coach</label>
-               <description>This represents the healthy home coach capable of reporting health
-                       index,temperature,humidity,pressure,air quality and sound level</description>
-               <channels>
-                       <channel id="HealthIndex" typeId="healthindex"/>
-                       <channel id="Co2" typeId="co2"/>
-                       <channel id="Temperature" typeId="temperature"/>
-                       <channel id="TempTrend" typeId="temperatureTrend"/>
-                       <channel id="Noise" typeId="noise"/>
-                       <channel id="Pressure" typeId="pressure"/>
-                       <channel id="PressTrend" typeId="pressureTrend"/>
-                       <channel id="AbsolutePressure" typeId="absolutePressure"/>
-                       <channel id="TimeStamp" typeId="timeUtc"/>
-                       <channel id="Humidity" typeId="humidity"/>
-                       <channel id="MinTemp" typeId="minTemp"/>
-                       <channel id="MaxTemp" typeId="maxTemp"/>
-                       <channel id="DateMinTemp" typeId="dateMinTemp"/>
-                       <channel id="DateMaxTemp" typeId="dateMaxTemp"/>
-                       <!-- Common to all devices -->
-                       <channel id="LastStatusStore" typeId="lastStatusStore"/>
-                       <channel id="WifiStatus" typeId="system.signal-strength"/>
-                       <channel id="Location" typeId="location"/>
-               </channels>
-
-               <properties>
-                       <property name="signalLevels">99,84,69,54</property>
-                       <property name="refreshPeriod">auto</property>
-               </properties>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:station"/>
-       </thing-type>
-</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/security.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/security.xml
new file mode 100644 (file)
index 0000000..60d3a7b
--- /dev/null
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="netatmo"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="security">
+               <label>Home Security</label>
+               <channels>
+                       <channel id="person-count" typeId="person-count"/>
+                       <channel id="unknown-person-count" typeId="unknown-person-count"/>
+                       <channel id="unknown-person-picture" typeId="unknown-person-picture"/>
+                       <channel id="home-event" typeId="home-event"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="status">
+               <label>Camera Status</label>
+               <channels>
+                       <channel id="monitoring" typeId="monitoring-status"/>
+                       <channel id="sd-card" typeId="sd-card-status"/>
+                       <channel id="alim" typeId="alim-status"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="live">
+               <label>Live Monitoring</label>
+               <channels>
+                       <channel id="picture" typeId="live-picture"/>
+                       <channel id="local-picture-url" typeId="live-picture-url">
+                               <label>Live Snapshot Local URL</label>
+                               <description>Local URL of the live snapshot for this camera.</description>
+                       </channel>
+                       <channel id="vpn-picture-url" typeId="live-picture-url">
+                               <label>Live Snapshot VPN URL</label>
+                               <description>URL of the live snapshot for this camera through Netatmo VPN.</description>
+                       </channel>
+                       <channel id="local-stream-url" typeId="live-stream-url">
+                               <label>Live Stream Local URL</label>
+                               <description>Local URL of the live stream for this camera.</description>
+                       </channel>
+                       <channel id="vpn-stream-url" typeId="live-stream-url">
+                               <label>Live Stream VPN URL</label>
+                               <description>URL of the live stream for this camera through Netatmo VPN.</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="last-event">
+               <label>Last Event</label>
+               <channels>
+                       <channel id="type" typeId="event-type"/>
+                       <channel id="subtype" typeId="event-subtype"/>
+                       <channel id="person-id" typeId="person-id"/>
+                       <channel id="video-status" typeId="video-status"/>
+                       <channel id="message" typeId="message"/>
+                       <channel id="time" typeId="timestamp">
+                               <label>Event Timestamp</label>
+                               <description>Moment when event occurred.</description>
+                       </channel>
+                       <channel id="snapshot" typeId="event-picture"/>
+                       <channel id="snapshot-url" typeId="event-picture-url"/>
+                       <channel id="local-video-url" typeId="video-url">
+                               <label>Video Local URL</label>
+                               <description>Local URL of the event recording.</description>
+                       </channel>
+                       <channel id="vpn-video-url" typeId="video-url">
+                               <label>Video VPN URL</label>
+                               <description>URL of the event recording through Netatmo VPN.</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="person-event">
+               <label>Last Event</label>
+               <channels>
+                       <channel id="subtype" typeId="event-subtype"/>
+                       <channel id="message" typeId="message">
+                               <description>Last event message from this person.</description>
+                       </channel>
+                       <channel id="time" typeId="timestamp">
+                               <label>Person Timestamp</label>
+                               <description>Moment of the last event for this person.</description>
+                       </channel>
+                       <channel id="snapshot" typeId="event-picture">
+                               <description>Picture of the last event for this person.</description>
+                       </channel>
+                       <channel id="snapshot-url" typeId="event-picture-url">
+                               <description>URL for the picture of the last event for this person.</description>
+                       </channel>
+                       <channel id="camera-id" typeId="camera-id"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="person">
+               <label>Person</label>
+               <channels>
+                       <channel id="avatar-url" typeId="avatar-picture-url"/>
+                       <channel id="avatar" typeId="avatar-picture"/>
+                       <channel id="at-home" typeId="at-home"/>
+                       <channel id="last-seen" typeId="timestamp">
+                               <label>Last Seen</label>
+                               <description>Moment when this person was last seen.</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="presence">
+               <label>Presence Camera</label>
+               <channels>
+                       <channel id="floodlight" typeId="floodlight-mode"/>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/station.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/station.xml
deleted file mode 100644 (file)
index aa19c9d..0000000
+++ /dev/null
@@ -1,295 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
-       <thing-type id="NAMain">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Main Indoor Station</label>
-               <description>This represents the main indoor module capable of reporting temperature,humidity,pressure,air quality and
-                       sound level</description>
-
-               <channels>
-                       <channel id="Co2" typeId="co2"/>
-                       <channel id="MinCo2" typeId="minCo2"/>
-                       <channel id="MinCo2ThisWeek" typeId="minCo2ThisWeek"/>
-                       <channel id="MinCo2ThisMonth" typeId="minCo2ThisMonth"/>
-                       <channel id="MaxCo2" typeId="maxCo2"/>
-                       <channel id="MaxCo2ThisWeek" typeId="maxCo2ThisWeek"/>
-                       <channel id="MaxCo2ThisMonth" typeId="maxCo2ThisMonth"/>
-                       <channel id="DateMinCo2" typeId="dateMinCo2"/>
-                       <channel id="DateMinCo2ThisWeek" typeId="dateMinCo2ThisWeek"/>
-                       <channel id="DateMinCo2ThisMonth" typeId="dateMinCo2ThisMonth"/>
-                       <channel id="DateMaxCo2" typeId="dateMaxCo2"/>
-                       <channel id="DateMaxCo2ThisWeek" typeId="dateMaxCo2ThisWeek"/>
-                       <channel id="DateMaxCo2ThisMonth" typeId="dateMaxCo2ThisMonth"/>
-                       <channel id="Temperature" typeId="temperature"/>
-                       <channel id="TempTrend" typeId="temperatureTrend"/>
-                       <channel id="Noise" typeId="noise"/>
-                       <channel id="MinNoise" typeId="minNoise"/>
-                       <channel id="MinNoiseThisWeek" typeId="minNoiseThisWeek"/>
-                       <channel id="MinNoiseThisMonth" typeId="minNoiseThisMonth"/>
-                       <channel id="MaxNoise" typeId="maxNoise"/>
-                       <channel id="MaxNoiseThisWeek" typeId="maxNoiseThisWeek"/>
-                       <channel id="MaxNoiseThisMonth" typeId="maxNoiseThisMonth"/>
-                       <channel id="DateMinNoise" typeId="dateMinNoise"/>
-                       <channel id="DateMinNoiseThisWeek" typeId="dateMinNoiseThisWeek"/>
-                       <channel id="DateMinNoiseThisMonth" typeId="dateMinNoiseThisMonth"/>
-                       <channel id="DateMaxNoise" typeId="dateMaxNoise"/>
-                       <channel id="DateMaxNoiseThisWeek" typeId="dateMaxNoiseThisWeek"/>
-                       <channel id="DateMaxNoiseThisMonth" typeId="dateMaxNoiseThisMonth"/>
-                       <channel id="Pressure" typeId="pressure"/>
-                       <channel id="MinPressure" typeId="minPressure"/>
-                       <channel id="MinPressureThisWeek" typeId="minPressureThisWeek"/>
-                       <channel id="MinPressureThisMonth" typeId="minPressureThisMonth"/>
-                       <channel id="MaxPressure" typeId="maxPressure"/>
-                       <channel id="MaxPressureThisWeek" typeId="maxPressureThisWeek"/>
-                       <channel id="MaxPressureThisMonth" typeId="maxPressureThisMonth"/>
-                       <channel id="DateMinPressure" typeId="dateMinPressure"/>
-                       <channel id="DateMinPressureThisWeek" typeId="dateMinPressureThisWeek"/>
-                       <channel id="DateMinPressureThisMonth" typeId="dateMinPressureThisMonth"/>
-                       <channel id="DateMaxPressure" typeId="dateMaxPressure"/>
-                       <channel id="DateMaxPressureThisWeek" typeId="dateMaxPressureThisWeek"/>
-                       <channel id="DateMaxPressureThisMonth" typeId="dateMaxPressureThisMonth"/>
-                       <channel id="PressTrend" typeId="pressureTrend"/>
-                       <channel id="AbsolutePressure" typeId="absolutePressure"/>
-                       <channel id="Humidity" typeId="humidity"/>
-                       <channel id="MinHumidity" typeId="minHumidity"/>
-                       <channel id="MinHumidityThisWeek" typeId="minHumidityThisWeek"/>
-                       <channel id="MinHumidityThisMonth" typeId="minHumidityThisMonth"/>
-                       <channel id="MaxHumidity" typeId="maxHumidity"/>
-                       <channel id="MaxHumidityThisWeek" typeId="maxHumidityThisWeek"/>
-                       <channel id="MaxHumidityThisMonth" typeId="maxHumidityThisMonth"/>
-                       <channel id="DateMinHumidity" typeId="dateMinHumidity"/>
-                       <channel id="DateMinHumidityThisWeek" typeId="dateMinHumidityThisWeek"/>
-                       <channel id="DateMinHumidityThisMonth" typeId="dateMinHumidityThisMonth"/>
-                       <channel id="DateMaxHumidity" typeId="dateMaxHumidity"/>
-                       <channel id="DateMaxHumidityThisWeek" typeId="dateMaxHumidityThisWeek"/>
-                       <channel id="DateMaxHumidityThisMonth" typeId="dateMaxHumidityThisMonth"/>
-                       <channel id="Humidex" typeId="humidex"/>
-                       <channel id="HeatIndex" typeId="heatIndex"/>
-                       <channel id="Dewpoint" typeId="dewPoint"/>
-                       <channel id="DewpointDepression" typeId="dewPointDepression"/>
-                       <channel id="MinTemp" typeId="minTemp"/>
-                       <channel id="MinTempThisWeek" typeId="minTempThisWeek"/>
-                       <channel id="MinTempThisMonth" typeId="minTempThisMonth"/>
-                       <channel id="MaxTemp" typeId="maxTemp"/>
-                       <channel id="MaxTempThisWeek" typeId="maxTempThisWeek"/>
-                       <channel id="MaxTempThisMonth" typeId="maxTempThisMonth"/>
-                       <channel id="DateMinTemp" typeId="dateMinTemp"/>
-                       <channel id="DateMinTempThisWeek" typeId="dateMinTempThisWeek"/>
-                       <channel id="DateMinTempThisMonth" typeId="dateMinTempThisMonth"/>
-                       <channel id="DateMaxTemp" typeId="dateMaxTemp"/>
-                       <channel id="DateMaxTempThisWeek" typeId="dateMaxTempThisWeek"/>
-                       <channel id="DateMaxTempThisMonth" typeId="dateMaxTempThisMonth"/>
-                       <!-- Common to all devices -->
-                       <channel id="TimeStamp" typeId="timeUtc"/>
-                       <channel id="LastStatusStore" typeId="lastStatusStore"/>
-                       <channel id="WifiStatus" typeId="system.signal-strength"/>
-                       <channel id="Location" typeId="location"/>
-               </channels>
-
-               <properties>
-                       <property name="signalLevels">99,84,69,54</property>
-                       <property name="refreshPeriod">auto</property>
-               </properties>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:station"/>
-       </thing-type>
-
-       <thing-type id="NAModule1">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Outdoor Module</label>
-               <description>This represents the outdoor module capable of reporting temperature and humidity</description>
-
-               <channels>
-                       <channel id="Temperature" typeId="temperature"/>
-                       <channel id="TempTrend" typeId="temperatureTrend"/>
-                       <channel id="Humidity" typeId="humidity"/>
-                       <channel id="MinHumidity" typeId="minHumidity"/>
-                       <channel id="MinHumidityThisWeek" typeId="minHumidityThisWeek"/>
-                       <channel id="MinHumidityThisMonth" typeId="minHumidityThisMonth"/>
-                       <channel id="MaxHumidity" typeId="maxHumidity"/>
-                       <channel id="MaxHumidityThisWeek" typeId="maxHumidityThisWeek"/>
-                       <channel id="MaxHumidityThisMonth" typeId="maxHumidityThisMonth"/>
-                       <channel id="DateMinHumidity" typeId="dateMinHumidity"/>
-                       <channel id="DateMinHumidityThisWeek" typeId="dateMinHumidityThisWeek"/>
-                       <channel id="DateMinHumidityThisMonth" typeId="dateMinHumidityThisMonth"/>
-                       <channel id="DateMaxHumidity" typeId="dateMaxHumidity"/>
-                       <channel id="DateMaxHumidityThisWeek" typeId="dateMaxHumidityThisWeek"/>
-                       <channel id="DateMaxHumidityThisMonth" typeId="dateMaxHumidityThisMonth"/>
-                       <channel id="Humidex" typeId="humidex"/>
-                       <channel id="HeatIndex" typeId="heatIndex"/>
-                       <channel id="Dewpoint" typeId="dewPoint"/>
-                       <channel id="DewpointDepression" typeId="dewPointDepression"/>
-                       <channel id="MinTemp" typeId="minTemp"/>
-                       <channel id="MinTempThisWeek" typeId="minTempThisWeek"/>
-                       <channel id="MinTempThisMonth" typeId="minTempThisMonth"/>
-                       <channel id="MaxTemp" typeId="maxTemp"/>
-                       <channel id="MaxTempThisWeek" typeId="maxTempThisWeek"/>
-                       <channel id="MaxTempThisMonth" typeId="maxTempThisMonth"/>
-                       <channel id="DateMinTemp" typeId="dateMinTemp"/>
-                       <channel id="DateMinTempThisWeek" typeId="dateMinTempThisWeek"/>
-                       <channel id="DateMinTempThisMonth" typeId="dateMinTempThisMonth"/>
-                       <channel id="DateMaxTemp" typeId="dateMaxTemp"/>
-                       <channel id="DateMaxTempThisWeek" typeId="dateMaxTempThisWeek"/>
-                       <channel id="DateMaxTempThisMonth" typeId="dateMaxTempThisMonth"/>
-                       <!-- Common to all modules -->
-                       <channel id="TimeStamp" typeId="timeUtc"/>
-                       <channel id="LastMessage" typeId="lastMessage"/>
-                       <channel id="LowBattery" typeId="system.low-battery"/>
-                       <channel id="BatteryVP" typeId="system.battery-level"/>
-                       <channel id="RfStatus" typeId="system.signal-strength"/>
-               </channels>
-
-               <properties>
-                       <property name="signalLevels">90,80,70,60</property>
-                       <property name="batteryLevels">3600,4500,6000</property>
-               </properties>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:module"/>
-       </thing-type>
-
-       <thing-type id="NAModule2">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Wind Gauge Module</label>
-               <description>This represents the wind module capable of reporting wind angle and strength</description>
-
-               <channels>
-                       <channel id="WindAngle" typeId="WindAngle"/>
-                       <channel id="WindStrength" typeId="WindStrength"/>
-                       <channel id="MaxWindStrength" typeId="MaxWindStrength"/>
-                       <channel id="DateMaxWindStrength" typeId="DateMaxWindStrength"/>
-                       <channel id="GustAngle" typeId="GustAngle"/>
-                       <channel id="GustStrength" typeId="GustStrength"/>
-                       <!-- Common to all modules -->
-                       <channel id="TimeStamp" typeId="timeUtc"/>
-                       <channel id="LastMessage" typeId="lastMessage"/>
-                       <channel id="LowBattery" typeId="system.low-battery"/>
-                       <channel id="BatteryVP" typeId="system.battery-level"/>
-                       <channel id="RfStatus" typeId="system.signal-strength"/>
-               </channels>
-
-               <properties>
-                       <property name="signalLevels">90,80,70,60</property>
-                       <property name="batteryLevels">3950,4770,6000</property>
-               </properties>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:module"/>
-       </thing-type>
-
-       <thing-type id="NAModule3">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Rain Gauge</label>
-               <description>This represents the Rain Gauge capable of measuring precipitation</description>
-
-               <channels>
-                       <channel id="Rain" typeId="rain"/>
-                       <channel id="SumRain1" typeId="rain1"/>
-                       <channel id="SumRain24" typeId="rain24"/>
-                       <channel id="SumRainThisWeek" typeId="rainThisWeek"/>
-                       <channel id="SumRainThisMonth" typeId="rainThisMonth"/>
-                       <!-- Common to all modules -->
-                       <channel id="TimeStamp" typeId="timeUtc"/>
-                       <channel id="LastMessage" typeId="lastMessage"/>
-                       <channel id="LowBattery" typeId="system.low-battery"/>
-                       <channel id="BatteryVP" typeId="system.battery-level"/>
-                       <channel id="RfStatus" typeId="system.signal-strength"/>
-               </channels>
-
-               <properties>
-                       <property name="signalLevels">90,80,70,60</property>
-                       <property name="batteryLevels">3600,4500,6000</property>
-               </properties>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:module"/>
-       </thing-type>
-
-       <thing-type id="NAModule4">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Additional Module</label>
-               <description>This represents an additional indoor module capable of reporting temperature, humidity and CO2 level</description>
-
-               <channels>
-                       <channel id="Co2" typeId="co2"/>
-                       <channel id="MinCo2" typeId="minCo2"/>
-                       <channel id="MinCo2ThisWeek" typeId="minCo2ThisWeek"/>
-                       <channel id="MinCo2ThisMonth" typeId="minCo2ThisMonth"/>
-                       <channel id="MaxCo2" typeId="maxCo2"/>
-                       <channel id="MaxCo2ThisWeek" typeId="maxCo2ThisWeek"/>
-                       <channel id="MaxCo2ThisMonth" typeId="maxCo2ThisMonth"/>
-                       <channel id="DateMinCo2" typeId="dateMinCo2"/>
-                       <channel id="DateMinCo2ThisWeek" typeId="dateMinCo2ThisWeek"/>
-                       <channel id="DateMinCo2ThisMonth" typeId="dateMinCo2ThisMonth"/>
-                       <channel id="DateMaxCo2" typeId="dateMaxCo2"/>
-                       <channel id="DateMaxCo2ThisWeek" typeId="dateMaxCo2ThisWeek"/>
-                       <channel id="DateMaxCo2ThisMonth" typeId="dateMaxCo2ThisMonth"/>
-                       <channel id="Temperature" typeId="temperature"/>
-                       <channel id="TempTrend" typeId="temperatureTrend"/>
-                       <channel id="Humidity" typeId="humidity"/>
-                       <channel id="MinHumidity" typeId="minHumidity"/>
-                       <channel id="MinHumidityThisWeek" typeId="minHumidityThisWeek"/>
-                       <channel id="MinHumidityThisMonth" typeId="minHumidityThisMonth"/>
-                       <channel id="MaxHumidity" typeId="maxHumidity"/>
-                       <channel id="MaxHumidityThisWeek" typeId="maxHumidityThisWeek"/>
-                       <channel id="MaxHumidityThisMonth" typeId="maxHumidityThisMonth"/>
-                       <channel id="DateMinHumidity" typeId="dateMinHumidity"/>
-                       <channel id="DateMinHumidityThisWeek" typeId="dateMinHumidityThisWeek"/>
-                       <channel id="DateMinHumidityThisMonth" typeId="dateMinHumidityThisMonth"/>
-                       <channel id="DateMaxHumidity" typeId="dateMaxHumidity"/>
-                       <channel id="DateMaxHumidityThisWeek" typeId="dateMaxHumidityThisWeek"/>
-                       <channel id="DateMaxHumidityThisMonth" typeId="dateMaxHumidityThisMonth"/>
-                       <channel id="Humidex" typeId="humidex"/>
-                       <channel id="HeatIndex" typeId="heatIndex"/>
-                       <channel id="Dewpoint" typeId="dewPoint"/>
-                       <channel id="DewpointDepression" typeId="dewPointDepression"/>
-                       <channel id="MinTemp" typeId="minTemp"/>
-                       <channel id="MinTempThisWeek" typeId="minTempThisWeek"/>
-                       <channel id="MinTempThisMonth" typeId="minTempThisMonth"/>
-                       <channel id="MaxTemp" typeId="maxTemp"/>
-                       <channel id="MaxTempThisWeek" typeId="maxTempThisWeek"/>
-                       <channel id="MaxTempThisMonth" typeId="maxTempThisMonth"/>
-                       <channel id="DateMinTemp" typeId="dateMinTemp"/>
-                       <channel id="DateMinTempThisWeek" typeId="dateMinTempThisWeek"/>
-                       <channel id="DateMinTempThisMonth" typeId="dateMinTempThisMonth"/>
-                       <channel id="DateMaxTemp" typeId="dateMaxTemp"/>
-                       <channel id="DateMaxTempThisWeek" typeId="dateMaxTempThisWeek"/>
-                       <channel id="DateMaxTempThisMonth" typeId="dateMaxTempThisMonth"/>
-                       <!-- Common to all modules -->
-                       <channel id="TimeStamp" typeId="timeUtc"/>
-                       <channel id="LastMessage" typeId="lastMessage"/>
-                       <channel id="LowBattery" typeId="system.low-battery"/>
-                       <channel id="BatteryVP" typeId="system.battery-level"/>
-                       <channel id="RfStatus" typeId="system.signal-strength"/>
-               </channels>
-
-               <properties>
-                       <property name="signalLevels">90,80,70,60</property>
-                       <property name="batteryLevels">4200,4920,6000</property>
-               </properties>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:module"/>
-       </thing-type>
-
-</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/thermostat.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/thermostat.xml
deleted file mode 100644 (file)
index b998b87..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
-       <!-- Netatmo Thermostat and Relay/Plug -->
-       <thing-type id="NAPlug">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Thermostat Relay/Plug</label>
-               <description>This represents the thermostat relay</description>
-
-               <channels>
-                       <channel id="ConnectedBoiler" typeId="connectedBoiler"/>
-                       <channel id="LastPlugSeen" typeId="lastPlugSeen"/>
-                       <channel id="LastBilan" typeId="lastBilan"/>
-                       <!-- Common to all devices -->
-                       <channel id="LastStatusStore" typeId="lastStatusStore"/>
-                       <channel id="WifiStatus" typeId="system.signal-strength"/>
-                       <channel id="Location" typeId="location"/>
-               </channels>
-
-               <properties>
-                       <property name="signalLevels">99,84,69,54</property>
-                       <property name="refreshPeriod">3600000</property>
-               </properties>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:plug"/>
-       </thing-type>
-
-       <thing-type id="NATherm1">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Thermostat Module</label>
-               <description>This represents the thermostat module itself</description>
-
-               <channels>
-                       <channel id="Temperature" typeId="temperature"/>
-                       <channel id="Sp_Temperature" typeId="setpointTemp"/>
-                       <channel id="SetpointMode" typeId="setpointMode"/>
-                       <channel id="ThermRelayCmd" typeId="ThermRelayCmd"/>
-                       <channel id="ThermOrientation" typeId="ThermOrientation"/>
-                       <channel id="TimeStamp" typeId="timeUtc"/>
-                       <channel id="Planning" typeId="planning"/>
-                       <channel id="SetpointEndTime" typeId="setpointEndTime"/>
-
-                       <!-- Common to all modules -->
-                       <channel id="LastMessage" typeId="lastMessage"/>
-                       <channel id="LowBattery" typeId="system.low-battery"/>
-                       <channel id="BatteryVP" typeId="system.battery-level"/>
-                       <channel id="RfStatus" typeId="system.signal-strength"/>
-               </channels>
-
-               <properties>
-                       <property name="signalLevels">90,80,70,60</property>
-                       <property name="batteryLevels">2700,3300,4500</property>
-               </properties>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:natherm1"/>
-       </thing-type>
-
-</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/weather.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/weather.xml
new file mode 100644 (file)
index 0000000..3e23183
--- /dev/null
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="netatmo"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <channel-group-type id="temperature-extended">
+               <label>Temperature</label>
+               <channels>
+                       <channel id="value" typeId="system.indoor-temperature"/>
+                       <channel id="min-today" typeId="min-temp"/>
+                       <channel id="max-today" typeId="max-temp"/>
+                       <channel id="min-time" typeId="timestamp-advanced">
+                               <label>Today Min Timestamp</label>
+                               <description>Moment when temperature was measured at its minimum today.</description>
+                       </channel>
+                       <channel id="max-time" typeId="timestamp-advanced">
+                               <label>Today Max Timestamp</label>
+                               <description>Moment when temperature was measured at its maximum today.</description>
+                       </channel>
+                       <channel id="trend" typeId="trend">
+                               <label>Temperature Trend</label>
+                       </channel>
+                       <channel id="heat-index" typeId="heat-index"/>
+                       <channel id="dewpoint" typeId="dewpoint"/>
+                       <channel id="dewpoint-depression" typeId="dewpoint-depression"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="temperature-outside">
+               <label>Temperature</label>
+               <channels>
+                       <channel id="value" typeId="system.outdoor-temperature"/>
+                       <channel id="min-today" typeId="min-temp"/>
+                       <channel id="max-today" typeId="max-temp"/>
+                       <channel id="min-time" typeId="timestamp-advanced">
+                               <label>Today Min Timestamp</label>
+                               <description>Moment when temperature was measured at its minimum today.</description>
+                       </channel>
+                       <channel id="max-time" typeId="timestamp-advanced">
+                               <label>Today Max Timestamp</label>
+                               <description>Moment when temperature was measured at its maximum today.</description>
+                       </channel>
+                       <channel id="trend" typeId="trend">
+                               <label>Temperature Trend</label>
+                       </channel>
+                       <channel id="heat-index" typeId="heat-index"/>
+                       <channel id="dewpoint" typeId="dewpoint"/>
+                       <channel id="dewpoint-depression" typeId="dewpoint-depression"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="humidity">
+               <label>Humidity</label>
+               <channels>
+                       <channel id="value" typeId="system.atmospheric-humidity"/>
+                       <channel id="humidex" typeId="humidex"/>
+                       <channel id="humidex-scale" typeId="humidex-scale"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="airquality">
+               <label>Air Quality</label>
+               <channels>
+                       <channel id="co2" typeId="co2"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="noise">
+               <label>Noise</label>
+               <channels>
+                       <channel id="value" typeId="noise"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="pressure-extended">
+               <label>Pressure</label>
+               <channels>
+                       <channel id="value" typeId="system.barometric-pressure"/>
+                       <channel id="absolute" typeId="absolute-pressure"/>
+                       <channel id="trend" typeId="trend">
+                               <label>Pressure Trend</label>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="rain">
+               <label>Rain gauge</label>
+               <channels>
+                       <channel id="value" typeId="rain-intensity"/>
+                       <channel id="sum-1" typeId="rain-quantity">
+                               <label>Rain 1h</label>
+                               <description>Quantity of water over last hour.</description>
+                       </channel>
+                       <channel id="sum-24" typeId="rain-quantity">
+                               <label>Rain 24h</label>
+                               <description>Quantity of water during the current day.</description>
+                       </channel>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="wind">
+               <label>Wind</label>
+               <channels>
+                       <channel id="angle" typeId="system.wind-direction"/>
+                       <channel id="strength" typeId="system.wind-speed"/>
+                       <channel id="max-strength" typeId="max-wind-strength"/>
+                       <channel id="max-strength-date" typeId="timestamp-advanced">
+                               <label>Date Max Wind Strength</label>
+                               <description>Moment when max wind strength was recorded.</description>
+                       </channel>
+                       <channel id="gust-angle" typeId="gust-angle"/>
+                       <channel id="gust-strength" typeId="gust-strength"/>
+               </channels>
+       </channel-group-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/welcomehome.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/welcomehome.xml
deleted file mode 100644 (file)
index 9c6b59e..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
-       <thing-type id="NAWelcomeHome">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Welcome Home</label>
-               <description>This represents a home hosting a camera</description>
-
-               <channels>
-                       <channel id="welcomeHomeCity" typeId="homecity"></channel>
-                       <channel id="welcomeHomeCountry" typeId="homecountry"></channel>
-                       <channel id="welcomeHomeTimezone" typeId="hometimezone"></channel>
-
-                       <channel id="welcomeHomePersonCount" typeId="homepersoncount"></channel>
-                       <channel id="welcomeHomeUnknownCount" typeId="homeunknowncount"></channel>
-
-                       <channel id="welcomeEventType" typeId="type"></channel>
-                       <channel id="welcomeEventTime" typeId="time"></channel>
-                       <channel id="welcomeEventCameraId" typeId="camera_id"></channel>
-                       <channel id="welcomeEventPersonId" typeId="person_id"></channel>
-                       <channel id="welcomeEventSnapshot" typeId="snapshot"></channel>
-                       <channel id="welcomeEventSnapshotURL" typeId="snapshot_url"></channel>
-                       <channel id="welcomeEventVideoURL" typeId="video_url"></channel>
-                       <channel id="welcomeEventVideoStatus" typeId="video_status"></channel>
-                       <channel id="welcomeEventIsArrival" typeId="is_arrival"></channel>
-                       <channel id="welcomeEventMessage" typeId="message"></channel>
-                       <channel id="welcomeEventSubType" typeId="sub_type"></channel>
-                       <channel id="welcomeHomeEvent" typeId="homeEvent"></channel>
-
-                       <channel id="cameraEvent" typeId="cameraEvent"/>
-               </channels>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:welcomehome"/>
-       </thing-type>
-
-       <channel-type id="homecity">
-               <item-type>String</item-type>
-               <label>City</label>
-               <description>City of the home</description>
-               <state readOnly="true"></state>
-       </channel-type>
-       <channel-type id="homecountry">
-               <item-type>String</item-type>
-               <label>Country</label>
-               <description>Country of the home</description>
-               <state readOnly="false"></state>
-       </channel-type>
-       <channel-type id="hometimezone">
-               <item-type>String</item-type>
-               <label>Timezone</label>
-               <description>Timezone of the home</description>
-               <state readOnly="true"></state>
-       </channel-type>
-       <channel-type id="homepersoncount" advanced="false">
-               <item-type>Number</item-type>
-               <label>Person Counter</label>
-               <description>Total number of Persons that are at home</description>
-               <state readOnly="true" pattern="%d"></state>
-       </channel-type>
-       <channel-type id="homeunknowncount" advanced="true">
-               <item-type>Number</item-type>
-               <label>Unknown Person Counter</label>
-               <description>Count how many Unknown Persons are at home</description>
-               <state readOnly="true" pattern="%d"></state>
-       </channel-type>
-
-       <channel-type id="type">
-               <item-type>String</item-type>
-               <label>Type</label>
-               <description>Type of event. Go to the Welcome page for further details.</description>
-               <state readOnly="true"></state>
-       </channel-type>
-       <channel-type id="time">
-               <item-type>DateTime</item-type>
-               <label>Time</label>
-               <description>Time of occurrence of event</description>
-               <state readOnly="true"></state>
-       </channel-type>
-       <channel-type id="camera_id">
-               <item-type>String</item-type>
-               <label>Camera ID</label>
-               <description>Camera that detected the event</description>
-               <state readOnly="true"></state>
-       </channel-type>
-       <channel-type id="person_id">
-               <item-type>String</item-type>
-               <label>Person ID</label>
-               <description>Id of the person the event is about (if any)</description>
-               <state readOnly="true"></state>
-       </channel-type>
-       <channel-type id="snapshot_url" advanced="true">
-               <item-type>String</item-type>
-               <label>Snapshot URL</label>
-               <description>Url of the event snapshot</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="snapshot">
-               <item-type>Image</item-type>
-               <label>Event Snapshot</label>
-               <description>Event Snapshot</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="video_url" advanced="true">
-               <item-type>String</item-type>
-               <label>Video URL</label>
-               <description>URL of the event video</description>
-               <state readOnly="true"></state>
-       </channel-type>
-       <channel-type id="video_status">
-               <item-type>String</item-type>
-               <label>Video Status</label>
-               <description>Status of the video (recording, deleted or available)</description>
-               <state readOnly="true"></state>
-       </channel-type>
-       <channel-type id="is_arrival">
-               <item-type>Switch</item-type>
-               <label>Is Arrival</label>
-               <description>If person was considered "away" before being seen during this event</description>
-               <state readOnly="true"></state>
-       </channel-type>
-       <channel-type id="message">
-               <item-type>String</item-type>
-               <label>Message</label>
-               <description>Message sent by Netatmo corresponding to given event</description>
-               <state readOnly="true"></state>
-       </channel-type>
-       <channel-type id="sub_type" advanced="true">
-               <item-type>String</item-type>
-               <label>Sub Type</label>
-               <description>Sub-type of SD and Alim events. Go to Welcome page for further details.</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="cameraEvent">
-               <kind>trigger</kind>
-               <label>Camera Event</label>
-               <event>
-                       <options>
-                               <option value="ANIMAL">Animal detected</option>
-                               <option value="HUMAN">Human detected</option>
-                               <option value="MOVEMENT">Unspecified movement detected</option>
-                               <option value="VEHICLE">Vehicle detected</option>
-                       </options>
-               </event>
-       </channel-type>
-
-</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/welcomeperson.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/welcomeperson.xml
deleted file mode 100644 (file)
index ecf3eaf..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
-       <thing-type id="NAWelcomePerson">
-               <supported-bridge-type-refs>
-                       <bridge-type-ref id="netatmoapi"/>
-               </supported-bridge-type-refs>
-
-               <label>Welcome Person</label>
-               <description>This represents a person at home</description>
-
-               <channels>
-                       <channel id="welcomePersonLastSeen" typeId="last_seen"></channel>
-                       <channel id="welcomePersonAtHome" typeId="person_athome"></channel>
-                       <channel id="welcomePersonAvatarUrl" typeId="person_avatar_url"></channel>
-                       <channel id="welcomePersonAvatar" typeId="person_avatar"></channel>
-                       <channel id="welcomePersonLastEventMessage" typeId="person_eventmsg"></channel>
-                       <channel id="welcomePersonLastEventTime" typeId="person_eventtime"></channel>
-                       <channel id="welcomePersonLastEventUrl" typeId="person_event_url"></channel>
-                       <channel id="welcomePersonLastEvent" typeId="person_event"></channel>
-                       <channel id="welcomeHomeEvent" typeId="homeEvent"></channel>
-               </channels>
-
-               <representation-property>id</representation-property>
-               <config-description-ref uri="thing-type:netatmo:nawelcomeperson"/>
-       </thing-type>
-
-       <channel-type id="last_seen">
-               <item-type>DateTime</item-type>
-               <label>Last Seen</label>
-               <description>Time when this person was last seen</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="person_athome">
-               <item-type>Switch</item-type>
-               <label>At Home</label>
-               <description>Indicates if this person is known to be at home or not</description>
-       </channel-type>
-
-       <channel-type id="person_eventmsg">
-               <item-type>String</item-type>
-               <label>Last Event Message</label>
-               <description>Last Event message from this person</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="person_eventtime">
-               <item-type>DateTime</item-type>
-               <label>Last Event Time</label>
-               <description>Last Event message time for this person</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="person_avatar_url" advanced="true">
-               <item-type>String</item-type>
-               <label>Avatar URL</label>
-               <description>URL for the avatar of this person</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="person_avatar">
-               <item-type>Image</item-type>
-               <label>Avatar</label>
-               <description>Avatar of this person</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="person_event">
-               <item-type>Image</item-type>
-               <label>Last Event Picture</label>
-               <description>Picture of the last event for this person</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-       <channel-type id="person_event_url" advanced="true">
-               <item-type>String</item-type>
-               <label>Last Event URL</label>
-               <description>URL for the picture of the last event for this person</description>
-               <state readOnly="true"></state>
-       </channel-type>
-
-</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/MeasureTest.java b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/MeasureTest.java
new file mode 100644 (file)
index 0000000..87ed22e
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2010-2022 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.api;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.core.types.State;
+
+/**
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class MeasureTest {
+
+    @Test
+    public void testMeasurePrecision() {
+        State value = toQuantityType(25.0, MeasureClass.INSIDE_TEMPERATURE);
+        assertEquals("25 °C", value.toString());
+        value = toQuantityType(52.0, MeasureClass.INSIDE_TEMPERATURE);
+        assertEquals("50 °C", value.toString());
+        value = toQuantityType(-10.0, MeasureClass.INSIDE_TEMPERATURE);
+        assertEquals("0 °C", value.toString());
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/dto/NAObjectTest.java b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/dto/NAObjectTest.java
new file mode 100644 (file)
index 0000000..d9a82b3
--- /dev/null
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2010-2022 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.api.dto;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.*;
+
+import java.time.ZoneId;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.TrendDescription;
+import org.openhab.binding.netatmo.internal.deserialization.NADeserializer;
+import org.openhab.core.i18n.TimeZoneProvider;
+
+/**
+ * @author Gaël L'hopital - Initial contribution
+ */
+public class NAObjectTest {
+    private static NADeserializer gson;
+
+    @BeforeAll
+    public static void init() {
+        TimeZoneProvider timeZoneProvider = mock(TimeZoneProvider.class);
+        when(timeZoneProvider.getTimeZone()).thenReturn(ZoneId.systemDefault());
+        gson = new NADeserializer(timeZoneProvider);
+    }
+
+    @Test
+    public void testNAObject() throws Exception {
+        String naObject = "{id:\"5954e7f249c75f97428b7b23\",name:\"Your House\"}";
+        NAObject object = gson.deserialize(NAObject.class, naObject);
+        assertEquals(object.getName(), "Your House");
+        assertEquals(object.getId(), "5954e7f249c75f97428b7b23");
+    }
+
+    @Test
+    public void testWebHookEvent() throws NetatmoException {
+        String event = "{" + "  \"user_id\": \"5c810xxxxxxx45f4\"," + "  \"snapshot_id\": \"5d19bxxxxxx6380342\","
+                + "  \"snapshot_key\": \"f0134210ff83fxxxxxxxf770090a423d9a5\","
+                + "  \"snapshot_url\": \"https://netatmocameraimage.blob.core.windows.net/production/5d1xxxa5\","
+                + "  \"event_type\": \"movement\"," + "  \"camera_id\": \"70:exxxxxdd:a7\","
+                + "  \"device_id\": \"70:exxxxdd:a7\"," + "  \"home_id\": \"5c5d79xxxx08cd594\","
+                + "  \"home_name\": \"Boulogne Billan.\"," + "  \"event_id\": \"5d19baae369359e896380341\","
+                + "  \"message\": \"Boulogne Billan: Movement detected by Indoor Camera\","
+                + "  \"push_type\": \"NACamera-movement\"" + "}";
+        WebhookEvent object = gson.deserialize(WebhookEvent.class, event);
+        assertEquals(object.getEventType(), EventType.MOVEMENT);
+    }
+
+    @Test
+    public void testDashboardData() throws NetatmoException {
+        String dashboard = "{time_utc:1623160336,Temperature:22.1,CO2:511,"
+                + "Humidity:66,Noise:36,Pressure:1026.1,AbsolutePressure:1009.3,"
+                + "min_temp:20,max_temp:22.4,date_max_temp:1623147932,"
+                + "Sdate_min_temp:1623125249,pressure_trend:\"nonexistent\",temp_trend:\"stable\"}";
+        Dashboard object = gson.deserialize(Dashboard.class, dashboard);
+        assertEquals(511, object.getCo2(), 0);
+        assertEquals(TrendDescription.UNKNOWN, object.getPressureTrend());
+        assertEquals(TrendDescription.STABLE, object.getTempTrend());
+    }
+}
diff --git a/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryServiceTest.java b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryServiceTest.java
deleted file mode 100644 (file)
index 9e7d837..0000000
+++ /dev/null
@@ -1,282 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.discovery;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.*;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.quality.Strictness;
-import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.i18n.TranslationProvider;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ThingUID;
-
-import io.swagger.client.model.NAMain;
-import io.swagger.client.model.NAStationDataBody;
-import io.swagger.client.model.NAStationModule;
-
-/**
- * @author Sven Strohschein - Initial contribution
- */
-@ExtendWith(MockitoExtension.class)
-@MockitoSettings(strictness = Strictness.LENIENT)
-public class NetatmoModuleDiscoveryServiceTest {
-
-    private NetatmoModuleDiscoveryServiceAccessible service;
-    private NetatmoBridgeHandler bridgeHandlerSpy;
-
-    @BeforeEach
-    public void before() {
-        Bridge bridgeMock = mock(Bridge.class);
-        when(bridgeMock.getUID()).thenReturn(new ThingUID("netatmo", "bridge"));
-
-        bridgeHandlerSpy = spy(new NetatmoBridgeHandler(bridgeMock, null));
-
-        LocaleProvider localeProviderMock = mock(LocaleProvider.class);
-        TranslationProvider translationProvider = mock(TranslationProvider.class);
-
-        service = new NetatmoModuleDiscoveryServiceAccessible(bridgeHandlerSpy, localeProviderMock,
-                translationProvider);
-    }
-
-    @Test
-    public void testStartScanNothingActivated() {
-        service.startScan();
-
-        assertEquals(0, service.getDiscoveredThings().size());
-    }
-
-    @Test
-    public void testStartScanDiscoverWeatherStationNoStationsBody() {
-        activateDiscoveryWeatherStation();
-
-        service.startScan();
-
-        assertEquals(0, service.getDiscoveredThings().size());
-    }
-
-    @Test
-    public void testStartScanDiscoverWeatherStationNoStations() {
-        activateDiscoveryWeatherStation();
-
-        when(bridgeHandlerSpy.getStationsDataBody(null)).thenReturn(Optional.of(new NAStationDataBody()));
-        service.startScan();
-
-        assertEquals(0, service.getDiscoveredThings().size());
-    }
-
-    @Test
-    public void testStartScanDiscoverWeatherStationNoStationName() {
-        recordStationBody(createStation());
-
-        service.startScan();
-
-        List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
-        assertEquals(1, discoveredThings.size());
-        // Expected is only the type name, because a station name isn't available
-        assertEquals("NAMain", discoveredThings.get(0).getLabel());
-    }
-
-    @Test
-    public void testStartScanDiscoverWeatherStation() {
-        NAMain station = createStation();
-        station.setStationName("Neu Wulmstorf");
-
-        recordStationBody(station);
-
-        service.startScan();
-
-        List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
-        assertEquals(1, discoveredThings.size());
-        // Expected is the type name + station name, because both are available
-        // and the station name contains only the city name by default which wouldn't be sufficient.
-        assertEquals("NAMain Neu Wulmstorf", discoveredThings.get(0).getLabel());
-    }
-
-    @Test
-    public void testStartScanDiscoverWeatherStationNoStationNameFavorite() {
-        NAMain station = createStation();
-        station.setFavorite(true);
-
-        recordStationBody(station);
-
-        service.startScan();
-
-        List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
-        assertEquals(1, discoveredThings.size());
-        // Expected is "(favorite)" within the label to make clear that it is favorite station
-        // (and not the station of the user)
-        assertEquals("NAMain (favorite)", discoveredThings.get(0).getLabel());
-    }
-
-    @Test
-    public void testStartScanDiscoverWeatherStationFavorite() {
-        NAMain station = createStation();
-        station.setStationName("Neu Wulmstorf");
-        station.setFavorite(true);
-
-        recordStationBody(station);
-
-        service.startScan();
-
-        List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
-        assertEquals(1, discoveredThings.size());
-        // Expected is "(favorite)" within the label to make clear that it is favorite station
-        // (and not the station of the user)
-        assertEquals("NAMain Neu Wulmstorf (favorite)", discoveredThings.get(0).getLabel());
-    }
-
-    @Test
-    public void testStartScanDiscoverWeatherStationModuleNoModuleName() {
-        NAMain station = createStation(createModule());
-        station.setStationName("Neu Wulmstorf");
-
-        recordStationBody(station);
-
-        service.startScan();
-
-        List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
-        assertEquals(2, discoveredThings.size());
-        assertEquals("NAMain Neu Wulmstorf", discoveredThings.get(0).getLabel());
-        // Expected is the type name + station name to make clear that the module belongs to the station.
-        // The module name isn't available, therefore the type name of the module is used.
-        assertEquals("NAModule1 Neu Wulmstorf", discoveredThings.get(1).getLabel());
-    }
-
-    @Test
-    public void testStartScanDiscoverWeatherStationModule() {
-        NAStationModule module = createModule();
-        module.setModuleName("Outdoor-Module");
-
-        NAMain station = createStation(module);
-        station.setStationName("Neu Wulmstorf");
-
-        recordStationBody(station);
-
-        service.startScan();
-
-        List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
-        assertEquals(2, discoveredThings.size());
-        assertEquals("NAMain Neu Wulmstorf", discoveredThings.get(0).getLabel());
-        // Expected is the module name + station name to make clear that the module belongs to the station.
-        // Because an explicit module name is available, the module type name isn't required.
-        assertEquals("Outdoor-Module Neu Wulmstorf", discoveredThings.get(1).getLabel());
-    }
-
-    @Test
-    public void testStartScanDiscoverWeatherStationModuleNoModuleNameFavorite() {
-        NAMain station = createStation(createModule());
-        station.setStationName("Neu Wulmstorf");
-        station.setFavorite(true);
-
-        recordStationBody(station);
-
-        service.startScan();
-
-        List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
-        assertEquals(2, discoveredThings.size());
-        assertEquals("NAMain Neu Wulmstorf (favorite)", discoveredThings.get(0).getLabel());
-        // Expected is "(favorite)" within the label to make clear that it is favorite station
-        // (and not the station of the user)
-        assertEquals("NAModule1 Neu Wulmstorf (favorite)", discoveredThings.get(1).getLabel());
-    }
-
-    @Test
-    public void testStartScanDiscoverWeatherStationModuleFavorite() {
-        NAStationModule module = createModule();
-        module.setModuleName("Outdoor-Module");
-
-        NAMain station = createStation(module);
-        station.setStationName("Neu Wulmstorf");
-        station.setFavorite(true);
-
-        recordStationBody(station);
-
-        service.startScan();
-
-        List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
-        assertEquals(2, discoveredThings.size());
-        assertEquals("NAMain Neu Wulmstorf (favorite)", discoveredThings.get(0).getLabel());
-        // Expected is "(favorite)" within the label to make clear that it is favorite station
-        // (and not the station of the user)
-        assertEquals("Outdoor-Module Neu Wulmstorf (favorite)", discoveredThings.get(1).getLabel());
-    }
-
-    private void recordStationBody(NAMain station) {
-        activateDiscoveryWeatherStation();
-
-        NAStationDataBody stationsBody = new NAStationDataBody();
-        stationsBody.setDevices(Collections.singletonList(station));
-
-        when(bridgeHandlerSpy.getStationsDataBody(null)).thenReturn(Optional.of(stationsBody));
-    }
-
-    private void activateDiscoveryWeatherStation() {
-        bridgeHandlerSpy.configuration.readStation = true;
-    }
-
-    private static NAMain createStation() {
-        NAMain station = new NAMain();
-        station.setId("01:00:00:00:00:aa");
-        station.setType("NAMain");
-        return station;
-    }
-
-    private static NAMain createStation(NAStationModule module) {
-        NAMain station = createStation();
-        station.setModules(Collections.singletonList(module));
-        return station;
-    }
-
-    private static NAStationModule createModule() {
-        NAStationModule module = new NAStationModule();
-        module.setId("01:00:00:00:01:aa");
-        module.setType("NAModule1");
-        return module;
-    }
-
-    @NonNullByDefault
-    private static class NetatmoModuleDiscoveryServiceAccessible extends NetatmoModuleDiscoveryService {
-
-        private final List<DiscoveryResult> discoveredThings;
-
-        private NetatmoModuleDiscoveryServiceAccessible(NetatmoBridgeHandler netatmoBridgeHandler,
-                LocaleProvider localeProvider, TranslationProvider translationProvider) {
-            super(netatmoBridgeHandler, localeProvider, translationProvider);
-            discoveredThings = new ArrayList<>();
-        }
-
-        @Override
-        protected void thingDiscovered(DiscoveryResult discoveryResult) {
-            super.thingDiscovered(discoveryResult);
-            discoveredThings.add(discoveryResult);
-        }
-
-        private List<DiscoveryResult> getDiscoveredThings() {
-            return discoveredThings;
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/presence/NAPresenceCameraHandlerTest.java b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/presence/NAPresenceCameraHandlerTest.java
deleted file mode 100644 (file)
index a9d2559..0000000
+++ /dev/null
@@ -1,494 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.presence;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
-
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.quality.Strictness;
-import org.openhab.binding.netatmo.internal.NetatmoBindingConstants;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.internal.ThingImpl;
-import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-import io.swagger.client.model.NAWelcomeCamera;
-
-/**
- * @author Sven Strohschein - Initial contribution
- */
-@ExtendWith(MockitoExtension.class)
-@MockitoSettings(strictness = Strictness.LENIENT)
-public class NAPresenceCameraHandlerTest {
-
-    private static final String DUMMY_VPN_URL = "https://dummytestvpnaddress.net/restricted/10.255.89.96/9826069dc689e8327ac3ed2ced4ff089/MTU5MTgzMzYwMDrQ7eHHhG0_OJ4TgmPhGlnK7QQ5pZ,,";
-    private static final String DUMMY_LOCAL_URL = "http://192.168.178.76/9826069dc689e8327ac3ed2ced4ff089";
-    private static final Optional<String> DUMMY_PING_RESPONSE = createPingResponseContent(DUMMY_LOCAL_URL);
-
-    private @Mock RequestExecutor requestExecutorMock;
-    private @Mock TimeZoneProvider timeZoneProviderMock;
-
-    private Thing presenceCameraThing;
-    private NAWelcomeCamera presenceCamera;
-    private ChannelUID cameraStatusChannelUID;
-    private ChannelUID floodlightChannelUID;
-    private ChannelUID floodlightAutoModeChannelUID;
-    private NAPresenceCameraHandlerAccessible handler;
-
-    @BeforeEach
-    public void before() {
-        presenceCameraThing = new ThingImpl(new ThingTypeUID("netatmo", "NOC"), "1");
-        presenceCamera = new NAWelcomeCamera();
-
-        cameraStatusChannelUID = new ChannelUID(presenceCameraThing.getUID(),
-                NetatmoBindingConstants.CHANNEL_CAMERA_STATUS);
-        floodlightChannelUID = new ChannelUID(presenceCameraThing.getUID(),
-                NetatmoBindingConstants.CHANNEL_CAMERA_FLOODLIGHT);
-        floodlightAutoModeChannelUID = new ChannelUID(presenceCameraThing.getUID(),
-                NetatmoBindingConstants.CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE);
-
-        handler = new NAPresenceCameraHandlerAccessible(presenceCameraThing, presenceCamera);
-    }
-
-    @Test
-    public void testHandleCommandSwitchSurveillanceOn() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(cameraStatusChannelUID, OnOffType.ON);
-
-        verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch on
-        verify(requestExecutorMock).executeGETRequest(DUMMY_LOCAL_URL + "/command/changestatus?status=on");
-    }
-
-    @Test
-    public void testHandleCommandSwitchSurveillanceOff() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(cameraStatusChannelUID, OnOffType.OFF);
-
-        verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
-        verify(requestExecutorMock).executeGETRequest(DUMMY_LOCAL_URL + "/command/changestatus?status=off");
-    }
-
-    @Test
-    public void testHandleCommandSwitchSurveillanceUnknownCommand() {
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(cameraStatusChannelUID, RefreshType.REFRESH);
-
-        verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
-                                                                       // command
-    }
-
-    @Test
-    public void testHandleCommandSwitchSurveillanceWithoutVPN() {
-        handler.handleCommand(cameraStatusChannelUID, OnOffType.ON);
-
-        verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed when no VPN
-                                                                       // address is set
-    }
-
-    @Test
-    public void testHandleCommandSwitchFloodlightOn() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
-        verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch on
-        verify(requestExecutorMock)
-                .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
-    }
-
-    @Test
-    public void testHandleCommandSwitchFloodlightOff() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
-
-        verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
-        verify(requestExecutorMock).executeGETRequest(
-                DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
-    }
-
-    @Test
-    public void testHandleCommandSwitchFloodlightOffWithAutoModeOn() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
-        assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-
-        handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
-
-        verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
-        verify(requestExecutorMock).executeGETRequest(
-                DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22auto%22%7D");
-    }
-
-    @Test
-    public void testHandleCommandSwitchFloodlightOnAddressChanged() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-        // 1.) execute ping + 2.) execute switch on
-        verify(requestExecutorMock, times(2)).executeGETRequest(any());
-        verify(requestExecutorMock)
-                .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
-
-        handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
-        // 1.) execute ping + 2.) execute switch on + 3.) execute switch off
-        verify(requestExecutorMock, times(3)).executeGETRequest(any());
-        verify(requestExecutorMock)
-                .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
-        verify(requestExecutorMock).executeGETRequest(
-                DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
-
-        final String newDummyVPNURL = DUMMY_VPN_URL + "2";
-        final String newDummyLocalURL = DUMMY_LOCAL_URL + "2";
-        final Optional<String> newDummyPingResponse = createPingResponseContent(newDummyLocalURL);
-
-        when(requestExecutorMock.executeGETRequest(newDummyVPNURL + "/command/ping")).thenReturn(newDummyPingResponse);
-
-        presenceCamera.setVpnUrl(newDummyVPNURL);
-        handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-        // 1.) execute ping + 2.) execute switch on + 3.) execute switch off + 4.) execute ping + 5.) execute switch on
-        verify(requestExecutorMock, times(5)).executeGETRequest(any());
-        verify(requestExecutorMock)
-                .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
-        verify(requestExecutorMock).executeGETRequest(
-                DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
-        verify(requestExecutorMock).executeGETRequest(
-                newDummyLocalURL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
-    }
-
-    @Test
-    public void testHandleCommandSwitchFloodlightUnknownCommand() {
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(floodlightChannelUID, RefreshType.REFRESH);
-
-        verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
-                                                                       // command
-    }
-
-    @Test
-    public void testHandleCommandSwitchFloodlightAutoModeOn() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-
-        handler.handleCommand(floodlightAutoModeChannelUID, OnOffType.ON);
-
-        verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch
-                                                                        // auto-mode on
-        verify(requestExecutorMock).executeGETRequest(
-                DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22auto%22%7D");
-    }
-
-    @Test
-    public void testHandleCommandSwitchFloodlightAutoModeOff() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-
-        handler.handleCommand(floodlightAutoModeChannelUID, OnOffType.OFF);
-
-        verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
-        verify(requestExecutorMock).executeGETRequest(
-                DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
-    }
-
-    @Test
-    public void testHandleCommandSwitchFloodlightAutoModeUnknownCommand() {
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(floodlightAutoModeChannelUID, RefreshType.REFRESH);
-
-        verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
-                                                                       // command
-    }
-
-    /**
-     * The request "fails" because there is no response content of the ping command.
-     */
-    @Test
-    public void testHandleCommandRequestFailed() {
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
-        verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
-    }
-
-    @Test
-    public void testHandleCommandWithoutVPN() {
-        handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
-        verify(requestExecutorMock, never()).executeGETRequest(any()); // no executions because the VPN URL is still
-                                                                       // unknown
-    }
-
-    @Test
-    public void testHandleCommandPingFailedNULLResponse() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(Optional.of(""));
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
-        verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
-    }
-
-    @Test
-    public void testHandleCommandPingFailedEmptyResponse() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(Optional.of(""));
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
-        verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
-    }
-
-    @Test
-    public void testHandleCommandPingFailedWrongResponse() {
-        when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping"))
-                .thenReturn(Optional.of("{ \"message\":  \"error\" }"));
-
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
-        verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
-    }
-
-    @Test
-    public void testHandleCommandModuleNULL() {
-        NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
-                timeZoneProviderMock);
-        handlerWithoutModule.handleCommand(floodlightChannelUID, OnOffType.ON);
-
-        verify(requestExecutorMock, never()).executeGETRequest(any()); // no executions because the thing isn't
-                                                                       // initialized
-    }
-
-    @Test
-    public void testGetNAThingPropertyCommonChannel() {
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_CAMERA_STATUS));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightOn() {
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
-        assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightOff() {
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightAuto() {
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
-        // When the floodlight is set to auto-mode it is currently off.
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightWithoutLightModeState() {
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightModuleNULL() {
-        NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
-                timeZoneProviderMock);
-        assertEquals(UnDefType.UNDEF, handlerWithoutModule.getNAThingProperty(floodlightChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightAutoModeFloodlightAuto() {
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
-        assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightAutoModeFloodlightOn() {
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
-        // When the floodlight is initially on (on starting the binding), there is no information about if the auto-mode
-        // was set before. Therefore the auto-mode is detected as deactivated / off.
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightAutoModeFloodlightOff() {
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
-        // When the floodlight is initially off (on starting the binding), the auto-mode isn't set.
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightScenarioWithAutoMode() {
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
-        assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
-
-        // The auto-mode was initially set, after that the floodlight was switched on by the user.
-        // In this case the binding should still know that the auto-mode is/was set.
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
-        assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-        assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
-
-        // After that the user switched off the floodlight.
-        // In this case the binding should still know that the auto-mode is/was set.
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
-        assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightScenarioWithoutAutoMode() {
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
-
-        // The auto-mode wasn't set, after that the floodlight was switched on by the user.
-        // In this case the binding should still know that the auto-mode isn't/wasn't set.
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-        assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
-
-        // After that the user switched off the floodlight.
-        // In this case the binding should still know that the auto-mode isn't/wasn't set.
-        presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-        assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetNAThingPropertyFloodlightAutoModeModuleNULL() {
-        NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
-                timeZoneProviderMock);
-        assertEquals(UnDefType.UNDEF, handlerWithoutModule.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-    }
-
-    @Test
-    public void testGetStreamURL() {
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
-        assertTrue(streamURL.isPresent());
-        assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index.m3u8", streamURL.get());
-    }
-
-    @Test
-    public void testGetStreamURLLocal() {
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        presenceCamera.setIsLocal(true);
-
-        Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
-        assertTrue(streamURL.isPresent());
-        assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index_local.m3u8", streamURL.get());
-    }
-
-    @Test
-    public void testGetStreamURLNotLocal() {
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-        presenceCamera.setIsLocal(false);
-
-        Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
-        assertTrue(streamURL.isPresent());
-        assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index.m3u8", streamURL.get());
-    }
-
-    @Test
-    public void testGetStreamURLWithoutVPN() {
-        Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
-        assertFalse(streamURL.isPresent());
-    }
-
-    @Test
-    public void testGetLivePictureURLState() {
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-
-        State livePictureURLState = handler.getLivePictureURLState();
-        assertEquals(new StringType(DUMMY_VPN_URL + "/live/snapshot_720.jpg"), livePictureURLState);
-    }
-
-    @Test
-    public void testGetLivePictureURLStateWithoutVPN() {
-        State livePictureURLState = handler.getLivePictureURLState();
-        assertEquals(UnDefType.UNDEF, livePictureURLState);
-    }
-
-    @Test
-    public void testGetLiveStreamState() {
-        presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-
-        State liveStreamState = handler.getLiveStreamState();
-        assertEquals(new StringType(DUMMY_VPN_URL + "/live/index.m3u8"), liveStreamState);
-    }
-
-    @Test
-    public void testGetLiveStreamStateWithoutVPN() {
-        State liveStreamState = handler.getLiveStreamState();
-        assertEquals(UnDefType.UNDEF, liveStreamState);
-    }
-
-    private static Optional<String> createPingResponseContent(final String localURL) {
-        return Optional.of("{\"local_url\":\"" + localURL + "\",\"product_name\":\"Welcome Netatmo\"}");
-    }
-
-    private interface RequestExecutor {
-
-        Optional<String> executeGETRequest(String url);
-    }
-
-    private class NAPresenceCameraHandlerAccessible extends NAPresenceCameraHandler {
-
-        private NAPresenceCameraHandlerAccessible(Thing thing, NAWelcomeCamera presenceCamera) {
-            super(thing, timeZoneProviderMock);
-            setModule(presenceCamera);
-        }
-
-        @Override
-        protected @NonNull Optional<@NonNull String> executeGETRequest(@NonNull String url) {
-            return requestExecutorMock.executeGETRequest(url);
-        }
-
-        @Override
-        protected @NonNull State getLivePictureURLState() {
-            return super.getLivePictureURLState();
-        }
-
-        @Override
-        protected @NonNull State getLiveStreamState() {
-            return super.getLiveStreamState();
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandlerTest.java b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandlerTest.java
deleted file mode 100644 (file)
index a6fa532..0000000
+++ /dev/null
@@ -1,372 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.welcome;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.quality.Strictness;
-import org.openhab.binding.netatmo.internal.NetatmoBindingConstants;
-import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
-import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.internal.ThingImpl;
-import org.openhab.core.types.UnDefType;
-
-import io.swagger.client.model.NAWelcomeEvent;
-import io.swagger.client.model.NAWelcomeHome;
-import io.swagger.client.model.NAWelcomeHomeData;
-import io.swagger.client.model.NAWelcomeSubEvent;
-
-/**
- * @author Sven Strohschein - Initial contribution
- */
-@ExtendWith(MockitoExtension.class)
-@MockitoSettings(strictness = Strictness.LENIENT)
-public class NAWelcomeHomeHandlerTest {
-
-    private static final String DUMMY_HOME_ID = "1";
-
-    private NAWelcomeHomeHandlerAccessible handler;
-
-    private @Mock NetatmoBridgeHandler bridgeHandlerMock;
-    private @Mock TimeZoneProvider timeZoneProviderMock;
-
-    @BeforeEach
-    public void before() {
-        Thing welcomeHomeThing = new ThingImpl(new ThingTypeUID("netatmo", "NAWelcomeHome"), "1");
-        handler = new NAWelcomeHomeHandlerAccessible(welcomeHomeThing);
-    }
-
-    @Test
-    public void testUpdateReadingsWithEvents() {
-        NAWelcomeEvent event1 = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.PERSON);
-        NAWelcomeEvent event2 = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
-
-        NAWelcomeHome home = new NAWelcomeHome();
-        home.setId(DUMMY_HOME_ID);
-        home.setEvents(Arrays.asList(event1, event2));
-
-        NAWelcomeHomeData homeData = new NAWelcomeHomeData();
-        homeData.setHomes(Collections.singletonList(home));
-
-        when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
-        handler.updateReadings();
-
-        // the second (last) event is expected
-        assertEquals(new StringType("movement"),
-                handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-
-        home.setEvents(Arrays.asList(event2, event1));
-        // the second (last) event is still expected (independent from the order of these are added)
-        assertEquals(new StringType("movement"),
-                handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-    }
-
-    @Test
-    public void testUpdateReadingsWith1Event() {
-        NAWelcomeEvent event = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.PERSON);
-
-        NAWelcomeHome home = new NAWelcomeHome();
-        home.setId(DUMMY_HOME_ID);
-        home.setEvents(Collections.singletonList(event));
-
-        NAWelcomeHomeData homeData = new NAWelcomeHomeData();
-        homeData.setHomes(Collections.singletonList(home));
-
-        when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
-        handler.updateReadings();
-
-        assertEquals(new StringType("person"),
-                handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-    }
-
-    @Test
-    public void testUpdateReadingsNoEvents() {
-        NAWelcomeHome home = new NAWelcomeHome();
-        home.setId(DUMMY_HOME_ID);
-
-        NAWelcomeHomeData homeData = new NAWelcomeHomeData();
-        homeData.setHomes(Collections.singletonList(home));
-
-        when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
-        handler.updateReadings();
-
-        assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-    }
-
-    @Test
-    public void testUpdateReadingsEmptyHomeData() {
-        NAWelcomeHomeData homeData = new NAWelcomeHomeData();
-
-        when(bridgeHandlerMock.getWelcomeDataBody(any())).thenReturn(Optional.of(homeData));
-
-        handler.updateReadings();
-
-        assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-    }
-
-    @Test
-    public void testUpdateReadingsNoHomeData() {
-        handler.updateReadings();
-
-        assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-    }
-
-    @Test
-    public void testTriggerChannelIfRequired() {
-        NAWelcomeEvent event1 = createPresenceEvent(1592661881, NAWelcomeSubEvent.TypeEnum.ANIMAL);
-        NAWelcomeEvent event2 = createPresenceEvent(1592661882, NAWelcomeSubEvent.TypeEnum.HUMAN);
-        NAWelcomeEvent event3 = createEvent(1592661883, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
-
-        NAWelcomeHome home = new NAWelcomeHome();
-        home.setId(DUMMY_HOME_ID);
-        home.setEvents(Collections.singletonList(event1));
-
-        NAWelcomeHomeData homeData = new NAWelcomeHomeData();
-        homeData.setHomes(Collections.singletonList(home));
-
-        when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
-        triggerCameraEvents();
-
-        // No triggered event is expected, because the binding is just started (with existing events).
-        assertEquals(0, handler.getTriggerChannelCount());
-
-        home.setEvents(Arrays.asList(event1, event2));
-
-        triggerCameraEvents();
-
-        // 1 triggered event is expected, because there is 1 new event since binding start (outdoor / detected human).
-        assertEquals(1, handler.getTriggerChannelCount());
-        assertEquals(new StringType("outdoor"),
-                handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-        assertEquals("HUMAN", handler.getLastDetectedObject());
-
-        home.setEvents(Arrays.asList(event1, event2));
-
-        triggerCameraEvents();
-
-        // No new triggered event is expected, because there are still the same events as before the refresh.
-        assertEquals(1, handler.getTriggerChannelCount());
-        assertEquals(new StringType("outdoor"),
-                handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-        assertEquals("HUMAN", handler.getLastDetectedObject());
-
-        home.setEvents(Arrays.asList(event1, event2, event3));
-
-        triggerCameraEvents();
-
-        // 1 new triggered event is expected (2 in sum), because there is 1 new event since the last triggered event
-        // (movement after outdoor / detected human).
-        assertEquals(2, handler.getTriggerChannelCount());
-        assertEquals(new StringType("movement"),
-                handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-        assertEquals("MOVEMENT", handler.getLastDetectedObject());
-    }
-
-    @Test
-    public void testTriggerChannelIfRequiredNoEventAvailable() {
-        NAWelcomeHome home = new NAWelcomeHome();
-        home.setId(DUMMY_HOME_ID);
-
-        NAWelcomeHomeData homeData = new NAWelcomeHomeData();
-        homeData.setHomes(Collections.singletonList(home));
-
-        when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
-        triggerCameraEvents();
-
-        // No triggered event is expected, because there aren't any events (the collection is NULL)
-        assertEquals(0, handler.getTriggerChannelCount());
-
-        home.setEvents(Collections.emptyList());
-
-        triggerCameraEvents();
-
-        // No triggered event is expected, because there aren't any events (the collection is empty)
-        assertEquals(0, handler.getTriggerChannelCount());
-    }
-
-    @Test
-    public void testTriggerChannelIfRequiredPersonMovement() {
-        NAWelcomeHome home = initHome();
-
-        NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
-        event.setPersonId("1");
-
-        home.getEvents().add(event);
-
-        triggerCameraEvents();
-
-        assertEquals(1, handler.getTriggerChannelCount());
-        assertEquals(new StringType("movement"),
-                handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-        assertEquals("HUMAN", handler.getLastDetectedObject());
-    }
-
-    @Test
-    public void testTriggerChannelIfRequiredHumanMovement() {
-        NAWelcomeHome home = initHome();
-
-        NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
-        event.setCategory(NAWelcomeEvent.CategoryEnum.HUMAN);
-
-        home.getEvents().add(event);
-
-        triggerCameraEvents();
-
-        assertEquals(1, handler.getTriggerChannelCount());
-        assertEquals(new StringType("movement"),
-                handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-        assertEquals("HUMAN", handler.getLastDetectedObject());
-    }
-
-    @Test
-    public void testTriggerChannelIfRequiredAnimalMovement() {
-        NAWelcomeHome home = initHome();
-
-        NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
-        event.setCategory(NAWelcomeEvent.CategoryEnum.ANIMAL);
-
-        home.getEvents().add(event);
-
-        triggerCameraEvents();
-
-        assertEquals(1, handler.getTriggerChannelCount());
-        assertEquals(new StringType("movement"),
-                handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-        assertEquals("ANIMAL", handler.getLastDetectedObject());
-    }
-
-    @Test
-    public void testTriggerChannelIfRequiredVehicleMovement() {
-        NAWelcomeHome home = initHome();
-
-        NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
-        event.setCategory(NAWelcomeEvent.CategoryEnum.VEHICLE);
-
-        home.getEvents().add(event);
-
-        triggerCameraEvents();
-
-        assertEquals(1, handler.getTriggerChannelCount());
-        assertEquals(new StringType("movement"),
-                handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-        assertEquals("VEHICLE", handler.getLastDetectedObject());
-    }
-
-    @Test
-    public void testMatchDetectedObjectEnums() {
-        assertArrayEquals(Arrays.stream(NAWelcomeEvent.CategoryEnum.values()).map(Enum::name).toArray(),
-                Arrays.stream(NAWelcomeSubEvent.TypeEnum.values()).map(Enum::name).toArray(),
-                "The detected object enums aren't equal anymore, that could lead to a bug! Please check the usages!");
-    }
-
-    private NAWelcomeHome initHome() {
-        NAWelcomeEvent initLastEvent = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
-
-        NAWelcomeHome home = new NAWelcomeHome();
-        home.setId(DUMMY_HOME_ID);
-
-        List<NAWelcomeEvent> events = new ArrayList<>();
-        events.add(initLastEvent);
-        home.setEvents(events);
-
-        NAWelcomeHomeData homeData = new NAWelcomeHomeData();
-        homeData.setHomes(Collections.singletonList(home));
-
-        when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
-        triggerCameraEvents();
-
-        return home;
-    }
-
-    private void triggerCameraEvents() {
-        handler.updateReadings();
-        handler.triggerChannelIfRequired(NetatmoBindingConstants.CHANNEL_CAMERA_EVENT);
-    }
-
-    private static NAWelcomeEvent createPresenceEvent(int eventTime, NAWelcomeSubEvent.TypeEnum detectedObjectType) {
-        NAWelcomeSubEvent subEvent = new NAWelcomeSubEvent();
-        subEvent.setTime(eventTime);
-        subEvent.setType(detectedObjectType);
-
-        NAWelcomeEvent event = createEvent(eventTime, NAWebhookCameraEvent.EventTypeEnum.OUTDOOR);
-        event.setEventList(Collections.singletonList(subEvent));
-        return event;
-    }
-
-    private static NAWelcomeEvent createEvent(int eventTime, NAWebhookCameraEvent.EventTypeEnum eventType) {
-        NAWelcomeEvent event = new NAWelcomeEvent();
-        event.setType(eventType.toString());
-        event.setTime(eventTime);
-        return event;
-    }
-
-    private class NAWelcomeHomeHandlerAccessible extends NAWelcomeHomeHandler {
-
-        private int triggerChannelCount;
-        private String lastDetectedObject;
-
-        private NAWelcomeHomeHandlerAccessible(Thing thing) {
-            super(thing, timeZoneProviderMock);
-        }
-
-        @Override
-        protected Optional<NetatmoBridgeHandler> getBridgeHandler() {
-            return Optional.of(bridgeHandlerMock);
-        }
-
-        @Override
-        protected String getId() {
-            return DUMMY_HOME_ID;
-        }
-
-        @Override
-        protected void triggerChannel(@NonNull String channelID, @NonNull String event) {
-            triggerChannelCount++;
-            lastDetectedObject = event;
-            super.triggerChannel(channelID, event);
-        }
-
-        private int getTriggerChannelCount() {
-            return triggerChannelCount;
-        }
-
-        public String getLastDetectedObject() {
-            return lastDetectedObject;
-        }
-    }
-}