From 43774bcbbac37a00ef44d28422550c9337f45d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Wed, 9 Jun 2021 09:56:08 +0200 Subject: [PATCH] Restarting Netatmo Binding update. This PR replaces PR #9486 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- bundles/org.openhab.binding.netatmo/NOTICE | 37 - bundles/org.openhab.binding.netatmo/README.md | 503 ++++------ bundles/org.openhab.binding.netatmo/pom.xml | 102 -- .../src/main/feature/feature.xml | 1 - .../binding/netatmo/internal/APIUtils.java | 41 - .../netatmo/internal/ChannelTypeUtils.java | 138 --- .../internal/NetatmoBindingConstants.java | 370 ++----- ...r.java => NetatmoDescriptionProvider.java} | 19 +- .../internal/NetatmoHandlerFactory.java | 202 ++-- .../netatmo/internal/action/RoomActions.java | 133 +++ .../netatmo/internal/api/AircareApi.java | 73 ++ .../netatmo/internal/api/ApiBridge.java | 280 ++++++ .../netatmo/internal/api/ApiOkResponse.java | 25 + .../netatmo/internal/api/ApiResponse.java | 34 + .../internal/api/AuthenticationApi.java | 83 ++ .../internal/api/ConnectionListener.java | 26 + .../internal/api/ConnectionStatus.java | 55 ++ .../netatmo/internal/api/EnergyApi.java | 131 +++ .../netatmo/internal/api/EventSubType.java | 74 ++ .../netatmo/internal/api/EventType.java | 132 +++ .../binding/netatmo/internal/api/HomeApi.java | 144 +++ .../netatmo/internal/api/ModuleType.java | 230 +++++ .../internal/api/NetatmoConstants.java | 317 ++++++ .../internal/api/NetatmoException.java | 73 ++ .../netatmo/internal/api/RestManager.java | 90 ++ .../netatmo/internal/api/SecurityApi.java | 122 +++ .../netatmo/internal/api/WeatherApi.java | 110 +++ .../api/dto/NAAccessTokenResponse.java | 125 +++ .../netatmo/internal/api/dto/NADashboard.java | 185 ++++ .../netatmo/internal/api/dto/NADevice.java | 69 ++ .../internal/api/dto/NADeviceDataBody.java | 40 + .../netatmo/internal/api/dto/NAEvent.java | 63 ++ .../netatmo/internal/api/dto/NAHome.java | 51 + .../netatmo/internal/api/dto/NAHomeData.java | 32 + .../internal/api/dto/NAHomeEnergy.java | 54 + .../netatmo/internal/api/dto/NAHomeEvent.java | 91 ++ .../internal/api/dto/NAHomeSecurity.java | 57 ++ .../internal/api/dto/NAHomeStatus.java | 27 + .../internal/api/dto/NAHomeWeather.java | 25 + .../internal/api/dto/NALastEventsData.java | 32 + .../netatmo/internal/api/dto/NAMain.java | 45 + .../internal/api/dto/NAMeasureBodyElem.java | 52 + .../netatmo/internal/api/dto/NAModule.java | 37 + .../netatmo/internal/api/dto/NAObject.java | 42 + .../netatmo/internal/api/dto/NAPerson.java | 60 ++ .../netatmo/internal/api/dto/NAPing.java | 39 + .../netatmo/internal/api/dto/NAPlace.java | 57 ++ .../netatmo/internal/api/dto/NAPlug.java | 49 + .../netatmo/internal/api/dto/NARoom.java | 94 ++ .../netatmo/internal/api/dto/NASetpoint.java | 41 + .../netatmo/internal/api/dto/NASnapshot.java | 56 ++ .../internal/api/dto/NAThermMeasure.java | 43 + .../internal/api/dto/NAThermProgram.java | 58 ++ .../internal/api/dto/NAThermostat.java | 75 ++ .../netatmo/internal/api/dto/NAThing.java | 88 ++ .../internal/api/dto/NATimeTableItem.java | 40 + .../internal/api/dto/NAWebhookEvent.java | 75 ++ .../netatmo/internal/api/dto/NAWelcome.java | 94 ++ .../netatmo/internal/api/dto/NAZone.java | 36 + .../internal/camera/CameraHandler.java | 246 ----- .../channelhelper/AbstractChannelHelper.java | 82 ++ .../internal/channelhelper/BatteryHelper.java | 66 +- .../channelhelper/CameraChannelHelper.java | 98 ++ .../channelhelper/Co2ChannelHelper.java | 46 + .../channelhelper/DeviceChannelHelper.java | 45 + .../channelhelper/HomeCoachChannelHelper.java | 45 + .../HomeEnergyChannelHelper.java | 101 ++ .../HomeSecurityChannelHelper.java | 93 ++ .../channelhelper/HumidityChannelHelper.java | 67 ++ .../channelhelper/LocationChannelHelper.java | 61 ++ .../channelhelper/MeasuresChannelHelper.java | 78 ++ .../channelhelper/NoiseChannelHelper.java | 46 + .../channelhelper/PersonChannelHelper.java | 60 ++ .../channelhelper/PlugChannelHelper.java | 54 + .../channelhelper/PresenceChannelHelper.java | 72 ++ .../channelhelper/PressureChannelHelper.java | 54 + .../internal/channelhelper/RadioHelper.java | 85 -- .../channelhelper/RainChannelHelper.java | 54 + .../channelhelper/RoomChannelHelper.java | 57 ++ .../RoomSetpointChannelHelper.java | 79 ++ .../channelhelper/RoomTempChannelHelper.java | 49 + .../internal/channelhelper/SignalHelper.java | 61 ++ .../TemperatureChannelHelper.java | 60 ++ .../Therm1PropsChannelHelper.java | 57 ++ .../Therm1SetpointChannelHelper.java | 92 ++ .../Therm1TempChannelHelper.java | 55 ++ .../channelhelper/WindChannelHelper.java | 60 ++ .../internal/config/MeasureChannelConfig.java | 64 ++ .../config/NetatmoBindingConfiguration.java | 63 ++ .../config/NetatmoBridgeConfiguration.java | 37 - .../config/NetatmoThingConfiguration.java | 27 + .../deserialization/NADynamicObjectMap.java | 36 + .../NADynamicObjectMapDeserializer.java | 66 ++ .../deserialization/NAHomeDeserializer.java | 60 ++ .../internal/deserialization/NAObjectMap.java | 29 + .../NAObjectMapDeserializer.java | 52 + .../internal/deserialization/NAPushType.java | 41 + .../NAPushTypeDeserializer.java | 49 + .../StrictEnumTypeAdapterFactory.java | 71 ++ .../discovery/NetatmoDiscoveryService.java | 191 ++++ .../NetatmoModuleDiscoveryService.java | 253 ----- .../handler/AbstractNetatmoThingHandler.java | 268 ----- .../{camera => handler}/CameraAddress.java | 2 +- .../internal/handler/CameraHandler.java | 169 ++++ .../internal/handler/HomeCoachHandler.java | 48 + .../internal/handler/HomeEnergyHandler.java | 130 +++ .../internal/handler/HomeSecurityHandler.java | 187 ++++ .../netatmo/internal/handler/MainHandler.java | 49 + .../netatmo/internal/handler/NRVHandler.java | 63 ++ .../handler/NetatmoBridgeHandler.java | 408 -------- .../internal/handler/NetatmoDataListener.java | 32 - .../handler/NetatmoDeviceHandler.java | 417 ++++---- .../handler/NetatmoModuleHandler.java | 145 --- .../internal/handler/PersonHandler.java | 131 +++ .../netatmo/internal/handler/PlugHandler.java | 86 ++ .../internal/handler/PresenceHandler.java | 81 ++ .../{ => handler}/RefreshStrategy.java | 35 +- .../netatmo/internal/handler/RoomHandler.java | 152 +++ .../internal/handler/Therm1Handler.java | 76 ++ .../homecoach/NAHealthyHomeCoachHandler.java | 118 --- .../presence/NAPresenceCameraHandler.java | 125 --- .../internal/providers/BaseDsI18n.java | 117 +++ .../NetatmoDeviceThingTypeProvider.java | 129 +++ .../internal/station/NAMainHandler.java | 297 ------ .../internal/station/NAModule1Handler.java | 185 ---- .../internal/station/NAModule2Handler.java | 81 -- .../internal/station/NAModule3Handler.java | 104 -- .../internal/station/NAModule4Handler.java | 212 ---- .../internal/thermostat/NAPlugHandler.java | 92 -- .../internal/thermostat/NATherm1Handler.java | 320 ------ .../netatmo/internal/utils/BindingUtils.java | 37 + .../internal/utils/ChannelTypeUtils.java | 139 +++ .../internal/utils/NetatmoCalendarUtils.java | 80 ++ .../internal/{ => utils}/WeatherUtils.java | 14 +- .../webhook/NAWebhookCameraEvent.java | 155 --- .../webhook/NAWebhookCameraEventPerson.java | 52 - .../internal/webhook/NetatmoServlet.java | 166 ++++ .../webhook/WelcomeWebHookServlet.java | 123 --- .../welcome/NAWelcomeCameraHandler.java | 56 -- .../welcome/NAWelcomeHomeHandler.java | 270 ----- .../welcome/NAWelcomePersonHandler.java | 151 --- .../main/resources/OH-INF/binding/binding.xml | 36 +- .../main/resources/OH-INF/config/config.xml | 228 ++--- .../resources/OH-INF/i18n/netatmo.properties | 50 + .../OH-INF/i18n/netatmo_de.properties | 143 +-- .../OH-INF/i18n/netatmo_fr.properties | 210 ++-- .../OH-INF/thing/{bridge.xml => aircare.xml} | 12 +- .../main/resources/OH-INF/thing/camera.xml | 116 --- .../main/resources/OH-INF/thing/channels.xml | 931 ++++-------------- .../main/resources/OH-INF/thing/common.xml | 40 + .../OH-INF/thing/configurable-channels.xml | 41 + .../main/resources/OH-INF/thing/energy.xml | 115 +++ .../OH-INF/thing/healthyhomecoach.xml | 44 - .../main/resources/OH-INF/thing/security.xml | 153 +++ .../main/resources/OH-INF/thing/station.xml | 295 ------ .../resources/OH-INF/thing/thermostat.xml | 69 -- .../main/resources/OH-INF/thing/weather.xml | 126 +++ .../resources/OH-INF/thing/welcomehome.xml | 156 --- .../resources/OH-INF/thing/welcomeperson.xml | 86 -- .../internal/api/dto/NAObjectTest.java | 82 ++ .../NetatmoModuleDiscoveryServiceTest.java | 282 ------ .../presence/NAPresenceCameraHandlerTest.java | 494 ---------- .../welcome/NAWelcomeHomeHandlerTest.java | 372 ------- 163 files changed, 10058 insertions(+), 8064 deletions(-) delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/APIUtils.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/ChannelTypeUtils.java rename bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/{NATherm1StateDescriptionProvider.java => NetatmoDescriptionProvider.java} (53%) create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/action/RoomActions.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AircareApi.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiBridge.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiOkResponse.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiResponse.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ConnectionListener.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ConnectionStatus.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EnergyApi.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EventSubType.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EventType.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/HomeApi.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ModuleType.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoConstants.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoException.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/RestManager.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/SecurityApi.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAAccessTokenResponse.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADashboard.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADevice.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADeviceDataBody.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAEvent.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHome.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeData.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeEnergy.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeEvent.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeSecurity.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeWeather.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NALastEventsData.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMain.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMeasureBodyElem.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAModule.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAObject.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPerson.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPing.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPlace.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPlug.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NARoom.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NASetpoint.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NASnapshot.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermMeasure.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermProgram.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermostat.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThing.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NATimeTableItem.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAWebhookEvent.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAWelcome.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAZone.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/AbstractChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/CameraChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Co2ChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/DeviceChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeCoachChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeEnergyChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeSecurityChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HumidityChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/LocationChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/MeasuresChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/NoiseChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PersonChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PlugChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PresenceChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PressureChannelHelper.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RadioHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RainChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomSetpointChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomTempChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/SignalHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/TemperatureChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1PropsChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1SetpointChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1TempChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/WindChannelHelper.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/MeasureChannelConfig.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBindingConfiguration.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBridgeConfiguration.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoThingConfiguration.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADynamicObjectMap.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADynamicObjectMapDeserializer.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAHomeDeserializer.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMapDeserializer.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushType.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushTypeDeserializer.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/StrictEnumTypeAdapterFactory.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoDiscoveryService.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryService.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/AbstractNetatmoThingHandler.java rename bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/{camera => handler}/CameraAddress.java (97%) create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CameraHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeCoachHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeEnergyHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeSecurityHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/MainHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NRVHandler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoBridgeHandler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDataListener.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoModuleHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PersonHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PlugHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PresenceHandler.java rename bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/{ => handler}/RefreshStrategy.java (70%) create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/RoomHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/Therm1Handler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/homecoach/NAHealthyHomeCoachHandler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/presence/NAPresenceCameraHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/BaseDsI18n.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoDeviceThingTypeProvider.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAMainHandler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule1Handler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule2Handler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule3Handler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule4Handler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/thermostat/NAPlugHandler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/thermostat/NATherm1Handler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/BindingUtils.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/ChannelTypeUtils.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/NetatmoCalendarUtils.java rename bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/{ => utils}/WeatherUtils.java (87%) delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEvent.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEventPerson.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NetatmoServlet.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/WelcomeWebHookServlet.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeCameraHandler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandler.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomePersonHandler.java create mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties rename bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/{bridge.xml => aircare.xml} (60%) delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/camera.xml create mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/common.xml create mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/configurable-channels.xml create mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/energy.xml delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/healthyhomecoach.xml create mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/security.xml delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/station.xml delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/thermostat.xml create mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/weather.xml delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/welcomehome.xml delete mode 100644 bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/welcomeperson.xml create mode 100644 bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/dto/NAObjectTest.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryServiceTest.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/presence/NAPresenceCameraHandlerTest.java delete mode 100644 bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandlerTest.java diff --git a/bundles/org.openhab.binding.netatmo/NOTICE b/bundles/org.openhab.binding.netatmo/NOTICE index 33e1210cf574d..38d625e349232 100644 --- a/bundles/org.openhab.binding.netatmo/NOTICE +++ b/bundles/org.openhab.binding.netatmo/NOTICE @@ -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 diff --git a/bundles/org.openhab.binding.netatmo/README.md b/bundles/org.openhab.binding.netatmo/README.md index f210b919d4bef..f4f633b8c490e 100644 --- a/bundles/org.openhab.binding.netatmo/README.md +++ b/bundles/org.openhab.binding.netatmo/README.md @@ -7,27 +7,14 @@ The Netatmo binding integrates the following Netatmo products: - *Indoor Camera / Welcome*. Reports last event and persons at home, consult picture and video from event/camera. - *Outdoor Camera / Presence*. Reports last event, consult picture and video from event/camera. -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. - +See http://www.netatmo.com/ for details on their product. ## 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: @@ -37,48 +24,48 @@ The variables you will need to get to setup the binding are: * `` The password attached to the above username. -### 2. Bridge and Things Configuration +The binding has the following configuration options: -Once you will get needed informations from the Netatmo API, you will be able to configure bridge and things. +- **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 OH2 server from Internet. +- **reconnectInterval:** The reconnection interval to Netatmo API (in s). +- **backgroundDiscovery:** 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. -E.g. +Create a `/services/netatmo.cfg` file and use the above options like this: ``` -Bridge netatmo:netatmoapi:home [ clientId="", clientSecret="", username = "", 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" ] - ... -} +binding.netatmo:clientId=ezezsdfdssfhdreytr +binding.netatmo:clientSecret=dshfdkfdfkshdkj +binding.netatmo:username=mail@mail.com +binding.netatmo:password=dssdsdsd ``` +### 2. Things Configuration + ### 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 +74,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. @@ -134,89 +121,47 @@ Number Netatmo_Indoor_CO2 "CO2" { channel = "netatmo:NAMain:home |---------------------|----------------------|----------------------------------------------------------| | 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) | +| temperature | Number:Temperature | Current temperature | +| temperature-trend | 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 | +| pressure-trend | String | Pressure evolution trend for last 12h (up, down, stable) | +| absolute-pressure | 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 | +| humidex | Number | Computed Humidex index | +| heat-index | Number:Temperature | Computed Heat Index | +| dewpoint | Number:Temperature | Computed dewpoint temperature | +| dew-point-depression | Number:Temperature | Computed dewpoint depression | +| today-min | Number:Temperature | Minimum temperature on current day | +| today-max | Number:Temperature | Maximum temperature on current day | +| ever-min | DateTime | Date when minimum temperature was reached on current day | +| ever-max | DateTime | Date when maximum temperature was reached on current day | +| timestamp | DateTime | Timestamp when data was measured | +| last-seen | DateTime | Last status store | +| signal-strength | 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 | All these channels are read only. ### Weather Station Outdoor module -Example item for the **outdoor module** +Example item for the **outdoor module** ``` Number Netatmo_Outdoor_Temperature "Temperature" { channel = "netatmo:NAModule1:home:outside:Temperature" } @@ -226,49 +171,33 @@ Number Netatmo_Outdoor_Temperature "Temperature" { channel = "netatmo:NAModule1: | Channel ID | Item Type | Description | |---------------------|----------------------|----------------------------------------------------------| -| Temperature | Number:Temperature | Current temperature | -| TempTrend | String | Temperature evolution trend (up, down, stable) | -| Humidity | Number:Dimensionless | Current humidity | +| temperature | Number:Temperature | Current temperature | +| temperature-trend | 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 | +| humidex | Number | Computed Humidex index | +| heat-index | 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 | +|dew-point-depression | Number:Temperature | Computed dewpoint depression | +| today-min | Number:Temperature | Minimum temperature on current day | +| today-max | Number:Temperature | Maximum temperature on current day | | 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 | +| timestamp | DateTime | Timestamp when data was measured | +| last-message | 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) | +| battery-level | Number | Battery level | +| signal-strength | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | All these channels are read only. ### Weather Station Additional Indoor module -Example item for the **indoor module** +Example item for the **indoor module** ``` Number Netatmo_Indoor2_Temperature "Temperature" { channel = "netatmo:NAModule4:home:insidesupp:Temperature" } @@ -280,53 +209,29 @@ Number Netatmo_Indoor2_Temperature "Temperature" { channel = "netatmo:NAModule4: |---------------------|----------------------|----------------------------------------------------------| | 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 | +| temperature | Number:Temperature | Current temperature | +| temperature-trend | 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 | +| humidex | Number | Computed Humidex index | +| heat-index | 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 | +| dew-point-depression | Number:Temperature | Computed dewpoint depression | +| today-min | Number:Temperature | Minimum temperature on current day | +| today-max | Number:Temperature | Maximum temperature on current day | +| ever-min | DateTime | Date when minimum temperature was reached on current day | +| ever-max | DateTime | Date when maximum temperature was reached on current day | +| timestamp | DateTime | Timestamp when data was measured | +| last-message | 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) | +| battery-level | Number | Battery level | +| signal-strength | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | All these channels are read only. @@ -336,23 +241,21 @@ All these channels are read only. Example item for the **rain gauge** ``` -Number Netatmo_Rain_Current "Rain [%.1f mm]" { channel = "netatmo:NAModule3:home:rain:Rain" } +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 | +| rain | Number:Length | Quantity of water | +| rain-sum-1 | Number:Length | Quantity of water on last hour | +| rain-sum-24 | Number:Length | Quantity of water on last day | +| timestamp | DateTime | Timestamp when data was measured | +| last-message | 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) | +| battery-level | Number | Battery level | +| signal-strength | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | All these channels are read only. @@ -362,24 +265,24 @@ All these channels are read only. Example item for the **wind module**: ``` -Number Netatmo_Wind_Strength "Wind Strength [%.0f KPH]" { channel = "netatmo:NAModule2:home:wind:WindStrength" } +Number Netatmo_Wind_Strength "Wind Strength [%.0f KPH]" { channel = "netatmo:NAModule2:home:wind:wind-strength" } ``` **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 | +| wind-angle | Number:Angle | Current 5 minutes average wind direction | +| wind-strength | 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 | +| timestamp | DateTime | Timestamp when data was measured | +| last-message | 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 | +| battery-level | Number | Battery level | +| signal-strength | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | +| max-strength | Number:Speed | Maximum wind strength recorded | +| date-max-strength | DateTime | Timestamp when MaxWindStrength was recorded | All these channels are read only. @@ -389,30 +292,30 @@ All these channels are read only. Example item for the **Healthy Home Coach**: ``` -String Netatmo_LivingRoom_HomeCoach_HealthIndex "Climate" { channel = "netatmo:NHC:home:livingroom:HealthIndex" } +String Netatmo_LivingRoom_HomeCoach_HealthIndex "Climate" { channel = "netatmo:NHC:home:livingroom:health-index" } ``` **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 | +|---------------------|----------------------|-------------------------------------------------------------------------| +| health-index | Number | Health index (0 : healthy, 1 : fine, 2 : fair, 3 : poor, 4 : unhealthy) | +| Co2 | Number:Dimensionless | Air quality | +| temperature | Number:Temperature | Current temperature | +| temperature-trend | String | Temperature evolution trend (up, down, stable) | +| Noise | Number:Dimensionless | Current noise level | +| Pressure | Number:Pressure | Current pressure | +| pressure-trend | String | Pressure evolution trend for last 12h (up, down, stable) | +| absolute-pressure | Number:Pressure | Absolute pressure | +| humidity | Number:Dimensionless | Current humidity | +| today-min | Number:Temperature | Minimum temperature on current day | +| today-max | Number:Temperature | Maximum temperature on current day | +| ever-min | DateTime | Date when minimum temperature was reached on current day | +| ever-max | DateTime | Date when maximum temperature was reached on current day | +| timestamp | DateTime | Timestamp when data was measured | +| last-seen | DateTime | Last status store | +| RadioStatus | 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 | All these channels are read only. @@ -421,14 +324,15 @@ 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 ID | Item Type | Description | +|---------------------|-------------|----------------------------------------------------------| +| ConnectedBoiler | Contact | Plug connected boiler | +| last-seen | DateTime | Last plug seen | +| last-bilan | DateTime | Month of the last available thermostat bilan | +| signal-strength | 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 | +| setpoint-duration | Number:Time | Default setpoint duration when manually set. | +| planning | String | Id of the currently active planning when mode = program | All these channels are read only. @@ -439,20 +343,27 @@ All these channels are read only. | 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 | +| temperature | Number:Temperature | Current temperature | +| setpoint | Number:Temperature | Thermostat temperature setpoint | +| setpoint-mode | String | Chosen setpoint_mode (program, away, hg, manual, off, max) | | 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 | +| anticipating | Switch | Indicates is anticipating the schedule | +| ThermOrientation | Number:Angle | Physical orientation of the thermostat module | +| timestamp | DateTime | Timestamp when data was measured | +| setpoint-end | DateTime | Thermostat goes back to schedule after that timestamp | +| last-message | 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) | +| battery-level | Number | Battery level | +| signal-strength | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | + +All these channels except setpoint, setpoint-mode and planning are read only. + +### Valve Module -All these channels except Sp_Temperature, SetpointMode and Planning are read only. +**Supported channels for the Valve module:** + +| Channel ID | Item Type | Description | +|---------------------|--------------------|------------------------------------------------------------| ### Welcome Home @@ -533,7 +444,7 @@ Warnings: 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. +- 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 ID | Item Type | Read/Write | Description | |-----------------------------|-----------|------------|--------------------------------------------------------------| @@ -544,8 +455,8 @@ Warnings: | 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 | +| auto-mode | Switch | Read-write | When set the floodlight gets switched to auto instead of off | +| floodlight | Switch | Read-write | Switch for the floodlight | ### Welcome Person @@ -561,7 +472,7 @@ Person things are automatically created in discovery process for all known perso | Channel ID | Item Type | Description | |-------------------------------|-----------|--------------------------------------------------------| -| welcomePersonLastSeen | DateTime | Time when this person was last seen | +| last-seen | 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 | @@ -591,101 +502,59 @@ Bridge netatmo:netatmoapi:home "Netatmo API" [ clientId="*********", clientSecre ``` # Indoor Module -Number:Temperature Indoor_Temp "Temperature [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:Temperature" } -Number:Temperature Indoor_Min_Temp "Min Temperature Today [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MinTemp" } -Number:Temperature Indoor_Min_Temp_This_Week "Min Temperature This Week [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MinTempThisWeek" } -Number:Temperature Indoor_Min_Temp_This_Month "Min Temperature This Month [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MinTempThisMonth" } -Number:Temperature Indoor_Max_Temp "Max Temperature Today [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MaxTemp" } -Number:Temperature Indoor_Max_Temp_This_Week "Max Temperature This Week [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MaxTempThisWeek" } -Number:Temperature Indoor_Max_Temp_This_Month "Max Temperature This Month [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MaxTempThisMonth" } -DateTime Indoor_Min_Temp_TS "Min Temperature Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { 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]" { 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]" { channel = "netatmo:NAMain:home:inside:DateMinTempThisMonth" } -DateTime Indoor_Max_Temp_TS "Max Temperature Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { 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]" { 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]" { channel = "netatmo:NAMain:home:inside:DateMaxTempThisMonth" } -Number:Dimensionless Indoor_Humidity "Humidity [%d %unit%]" { channel = "netatmo:NAMain:home:inside:Humidity" } +Number:Temperature Indoor_Temp "Temperature [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:temperature" } +Number:Temperature Indoor_Min_Temp "Min Temperature Today [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:today-min" } +Number:Temperature Indoor_Max_Temp "Max Temperature Today [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:today-max" } +DateTime Indoor_Min_Temp_TS "Min Temperature Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:NAMain:home:inside:ever-min" } +DateTime Indoor_Max_Temp_TS "Max Temperature Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:NAMain:home:inside:ever-max" } +Number:Dimensionless Indoor_Humidity "Humidity [%d %unit%]" { channel = "netatmo:NAMain:home:inside:humidity" } Number:Dimensionless Indoor_Min_Humidity "Min Humidity Today [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MinHumidity" } -Number:Dimensionless Indoor_Min_Humidity_This_Week "Min Humidity This Week [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MinHumidityThisWeek" } -Number:Dimensionless Indoor_Min_Humidity_This_Month "Min Humidity This Month [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MinHumidityThisMonth" } Number:Dimensionless Indoor_Max_Humidity "Max Humidity Today [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MaxHumidity" } -Number:Dimensionless Indoor_Max_Humidity_This_Week "Max Humidity This Week [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MaxHumidityThisWeek" } -Number:Dimensionless Indoor_Max_Humidity_This_Month "Max Humidity This Month [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MaxHumidityThisMonth" } DateTime Indoor_Min_Humidity_TS "Min Humidity Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { 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]" { 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]" { channel = "netatmo:NAMain:home:inside:DateMinHumidityThisMonth" } DateTime Indoor_Max_Humidity_TS "Max Humidity Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { 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]" { 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]" { channel = "netatmo:NAMain:home:inside:DateMaxHumidityThisMonth" } -Number Indoor_Humidex "Humidex [%.0f]" { channel = "netatmo:NAMain:home:inside:Humidex" } -Number:Temperature Indoor_HeatIndex "HeatIndex [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:HeatIndex" } +Number Indoor_Humidex "Humidex [%.0f]" { channel = "netatmo:NAMain:home:inside:humidex" } +Number:Temperature Indoor_HeatIndex "HeatIndex [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:heat-index" } Number:Temperature Indoor_Dewpoint "Dewpoint [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:Dewpoint" } -Number:Temperature Indoor_DewpointDepression "DewpointDepression [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:DewpointDepression" } +Number:Temperature Indoor_DewpointDepression "DewpointDepression [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:dew-point-depression" } Number:Dimensionless Indoor_Co2 "CO2 [%d %unit%]" { channel = "netatmo:NAMain:home:inside:Co2" } Number:Dimensionless Indoor_Min_Co2 "Min CO2 Today [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MinCo2" } -Number:Dimensionless Indoor_Min_Co2_This_Week "Min CO2 This Week [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MinCo2ThisWeek" } -Number:Dimensionless Indoor_Min_Co2_This_Month "Min CO2 This Month [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MinCo2ThisMonth" } Number:Dimensionless Indoor_Max_Co2 "Max CO2 Today [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MaxCo2" } -Number:Dimensionless Indoor_Max_Co2_This_Week "Max CO2 This Week [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MaxCo2ThisWeek" } -Number:Dimensionless Indoor_Max_Co2_This_Month "Max CO2 This Month [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MaxCo2ThisMonth" } DateTime Indoor_Min_Co2_TS "Min CO2 Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { 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]" { 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]" { channel = "netatmo:NAMain:home:inside:DateMinCo2ThisMonth" } DateTime Indoor_Max_Co2_TS "Max CO2 Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { 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]" { 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]" { channel = "netatmo:NAMain:home:inside:DateMaxCo2ThisMonth" } Number:Pressure Indoor_Pressure "Pressure [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:Pressure" } Number:Pressure Indoor_Min_Pressure "Min Pressure Today [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MinPressure" } -Number:Pressure Indoor_Min_Pressure_This_Week "Min Pressure This Week [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MinPressureThisWeek" } -Number:Pressure Indoor_Min_Pressure_This_Month "Min Pressure This Month [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MinPressureThisMonth" } Number:Pressure Indoor_Max_Pressure "Max Pressure Today [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MaxPressure" } -Number:Pressure Indoor_Max_Pressure_This_Week "Max Pressure This Week [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MaxPressureThisWeek" } -Number:Pressure Indoor_Max_Pressure_This_Month "Max Pressure This Month [%d %unit%]" { channel = "netatmo:NAMain:home:inside:MaxPressureThisMonth" } DateTime Indoor_Min_Pressure_TS "Min Pressure Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { 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]" { 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]" { channel = "netatmo:NAMain:home:inside:DateMinPressureThisMonth" } DateTime Indoor_Max_Pressure_TS "Max Pressure Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { 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]" { 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]" { channel = "netatmo:NAMain:home:inside:DateMaxPressureThisMonth" } -Number:Pressure Indoor_AbsolutePressure "AbsolutePressure [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:AbsolutePressure" } +Number:Pressure Indoor_AbsolutePressure "AbsolutePressure [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:absolute-pressure" } Number:Dimensionless Indoor_Noise "Noise [%d %unit%]" { channel = "netatmo:NAMain:home:inside:Noise" } Number:Dimensionless Indoor_Min_Noise "Min Noise Today [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MinNoise" } -Number:Dimensionless Indoor_Min_Noise_This_Week "Min Noise This Week [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MinNoiseThisWeek" } -Number:Dimensionless Indoor_Min_Noise_This_Month "Min Noise This Month [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MinNoiseThisMonth" } Number:Dimensionless Indoor_Max_Noise "Max Noise Today [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MaxNoise" } -Number:Dimensionless Indoor_Max_Noise_This_Week "Max Noise This Week [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MaxNoiseThisWeek" } -Number:Dimensionless Indoor_Max_Noise_This_Month "Max Noise This Month [%.1f %unit%]" { channel = "netatmo:NAMain:home:inside:MaxNoiseThisMonth" } DateTime Indoor_Min_Noise_TS "Min Noise Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { 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]" { 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]" { channel = "netatmo:NAMain:home:inside:DateMinNoiseThisMonth" } DateTime Indoor_Max_Noise_TS "Max Noise Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { 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]" { 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]" { channel = "netatmo:NAMain:home:inside:DateMaxNoiseThisMonth" } -Number Indoor_WifiStatus "WifiStatus [%s]" { channel = "netatmo:NAMain:home:inside:WifiStatus" } -DateTime Indoor_TimeStamp "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:NAMain:home:inside:TimeStamp" } -Location Indoor_Location "Location" { channel = "netatmo:NAMain:home:inside:Location" } -DateTime Indoor_LastStatusStore "LastStatusStore [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:NAMain:home:inside:LastStatusStore" } +Number Indoor_RadioStatus "RadioStatus [%s]" { channel = "netatmo:NAMain:home:inside:RadioStatus" } +DateTime Indoor_TimeStamp "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:NAMain:home:inside:timestamp" } +Location Indoor_Location "Location" { channel = "netatmo:NAMain:home:inside:location" } +DateTime Indoor_LastSeen "LastSeen [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:NAMain:home:inside:last-seen" } # Outdoor Module -Number:Temperature Outdoor_Temperature "Temperature [%.1f %unit%]" { channel = "netatmo:NAModule1:home:outside:Temperature" } -String Outdoor_TempTrend "TempTrend [%s]" { channel = "netatmo:NAModule1:home:outside:TempTrend" } -Number:Dimensionless Outdoor_Humidity "Humidity [%d %unit%]" { channel = "netatmo:NAModule1:home:outside:Humidity" } -Number Outdoor_Humidex "Humidex [%.0f]" { channel = "netatmo:NAModule1:home:outside:Humidex" } -Number:Temperature Outdoor_HeatIndex "HeatIndex [%.1f %unit%]" { channel = "netatmo:NAModule1:home:outside:HeatIndex" } +Number:Temperature Outdoor_Temperature "Temperature [%.1f %unit%]" { channel = "netatmo:NAModule1:home:outside:temperature" } +String Outdoor_TempTrend "TempTrend [%s]" { channel = "netatmo:NAModule1:home:outside:temperature-trend" } +Number:Dimensionless Outdoor_Humidity "Humidity [%d %unit%]" { channel = "netatmo:NAModule1:home:outside:humidity" } +Number Outdoor_Humidex "Humidex [%.0f]" { channel = "netatmo:NAModule1:home:outside:humidex" } +Number:Temperature Outdoor_HeatIndex "heat-index [%.1f %unit%]" { channel = "netatmo:NAModule1:home:outside:HeatIndex" } Number:Temperature Outdoor_Dewpoint "Dewpoint [%.1f %unit%]" { channel = "netatmo:NAModule1:home:outside:Dewpoint" } -Number:Temperature Outdoor_DewpointDepression "DewpointDepression [%.1f %unit%]" { channel = "netatmo:NAModule1:home:outside:DewpointDepression" } -Number Outdoor_RfStatus "RfStatus [%.0f / 5]" { channel = "netatmo:NAModule1:home:outside:RfStatus" } +Number:Temperature Outdoor_DewpointDepression "DewpointDepression [%.1f %unit%]" { channel = "netatmo:NAModule1:home:outside:dew-point-depression" } +Number Outdoor_RadioStatus "RfStatus [%.0f / 5]" { channel = "netatmo:NAModule1:home:outside:signal-strength" } Switch Outdoor_LowBattery "LowBattery [%s]" { channel = "netatmo:NAModule1:home:outside:LowBattery" } -Number Outdoor_BatteryVP "BatteryVP [%.0f %%]" { channel = "netatmo:NAModule1:home:outside:BatteryVP" } -DateTime Outdoor_TimeStamp "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:NAModule1:home:outside:TimeStamp" } -DateTime Outdoor_LastMessage "LastMessage [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:NAModule1:home:outside:LastMessage" } +Number Outdoor_BatteryVP "BatteryVP [%.0f %%]" { channel = "netatmo:NAModule1:home:outside:battery-level" } +DateTime Outdoor_TimeStamp "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:NAModule1:home:outside:timestamp" } +DateTime Outdoor_LastMessage "LastMessage [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:NAModule1:home:outside:last-message" } # Rain Module -Number:Length Rain_Hour "Rain Last Hour [%.02f %unit%]" {channel="netatmo:NAModule3:home:rain:SumRain1"} -Number:Length Rain_Today "Rain Today [%.02f %unit%]" {channel="netatmo:NAModule3:home:rain:SumRain24"} -Number:Length Rain_Week "Rain This Week [%.02f %unit%]" {channel="netatmo:NAModule3:home:rain:SumRainThisWeek"} -Number:Length Rain_Month "Rain This Month [%.02f %unit%]" {channel="netatmo:NAModule3:home:rain:SumRainThisMonth"} -Number Rain_BatteryVP "Rain battery status [%d%%]" {channel="netatmo:NAModule3:home:rain:BatteryVP"} +Number:Length Rain_Hour "Rain Last Hour [%.02f %unit%]" {channel="netatmo:NAModule3:home:rain:rain-sum-1"} +Number:Length Rain_Today "Rain Today [%.02f %unit%]" {channel="netatmo:NAModule3:home:rain:rain-sum-24"} +Number Rain_BatteryVP "Rain battery status [%d%%]" {channel="netatmo:NAModule3:home:rain:battery-level"} ``` ## sitemaps/netatmo.sitemap @@ -695,30 +564,14 @@ 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 @@ -731,44 +584,24 @@ sitemap netatmo label="Netatmo" { 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,7 +609,7 @@ 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 @@ -798,7 +631,7 @@ sitemap netatmo label="Netatmo" { 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 +http://www.netatmo.com/en-US/addguest/index/TIQ3797dtfOmgpqUcct3/70:ee:50:00:02:20 # Icons diff --git a/bundles/org.openhab.binding.netatmo/pom.xml b/bundles/org.openhab.binding.netatmo/pom.xml index bef045a35f495..3e006ece389b5 100644 --- a/bundles/org.openhab.binding.netatmo/pom.xml +++ b/bundles/org.openhab.binding.netatmo/pom.xml @@ -14,106 +14,4 @@ openHAB Add-ons :: Bundles :: Netatmo Binding - - !android.*,!com.android.org.*,!org.apache.harmony.*,!sun.*,!org.apache.oltu.* - - - - - org.openhab.osgiify - org.json.json - 20131018 - compile - - - com.squareup.okhttp - okhttp - 2.7.5 - compile - - - com.google.android - * - - - - - com.squareup.okhttp - logging-interceptor - 2.7.5 - compile - - - com.google.android - * - - - - - com.squareup.okio - okio - 1.6.0 - compile - - - io.gsonfire - gson-fire - 1.8.4 - compile - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.client - 1.0.0 - compile - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.common - 1.0.0 - compile - - - commons-codec - commons-codec - 1.8 - compile - - - com.google.code.gson - gson - 2.8.5 - compile - - - - - - - io.swagger.codegen.v3 - swagger-codegen-maven-plugin - 3.0.21 - - - - generate - - - https://raw.githubusercontent.com/cbornet/netatmo-swagger-decl/35e27745fb0d432bc6c8b5ec7a83ed2a09944cea/spec/swagger.yaml - java - false - false - - src/main/java - true - java8-localdatetime - true - - - - - - - - diff --git a/bundles/org.openhab.binding.netatmo/src/main/feature/feature.xml b/bundles/org.openhab.binding.netatmo/src/main/feature/feature.xml index 21b41c9cde3ee..e031c4791014d 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.netatmo/src/main/feature/feature.xml @@ -4,7 +4,6 @@ openhab-runtime-base - mvn:org.openhab.osgiify/org.json.json/20131018 mvn:org.openhab.addons.bundles/org.openhab.binding.netatmo/${project.version} 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 index 482ae769c8780..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/APIUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 Stream nonNullStream(Collection collection) { - return Optional.ofNullable(collection).stream().flatMap(Collection::stream); - } - - public static List nonNullList(List 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 index 909187a1895c2..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/ChannelTypeUtils.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright (c) 2010-2021 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/NetatmoBindingConstants.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java index 89c8bc272aeb2..d580f3c553130 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java @@ -12,292 +12,126 @@ */ 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 * 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 SERVICE_PID = "org.openhab.binding." + BINDING_ID; 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"; + // Things properties + public static final String PROPERTY_MAX_EVENT_TIME = "last-event"; + + // Channel group ids + public static final String GROUP_TEMPERATURE = "temperature"; + public static final String GROUP_HUMIDITY = "humidity"; + public static final String GROUP_CO2 = "co2"; + public static final String GROUP_NOISE = "noise"; + public static final String GROUP_PRESSURE = "pressure"; + public static final String GROUP_DEVICE = "device"; + public static final String GROUP_LOCATION = "location"; + public static final String GROUP_RAIN = "rain"; + public static final String GROUP_WIND = "wind"; + public static final String GROUP_HEALTH = "health"; + public static final String GROUP_PLUG = "plug"; + public static final String GROUP_HOME_ENERGY = "energy"; + public static final String GROUP_SIGNAL = "signal"; + public static final String GROUP_BATTERY = "battery"; + public static final String GROUP_ENERGY_BATTERY = "energy-battery"; + public static final String GROUP_HOME_SECURITY = "home-security"; + public static final String GROUP_WELCOME = "welcome"; + public static final String GROUP_PRESENCE = "presence"; + public static final String GROUP_WELCOME_EVENT = "welcome-event"; + 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_TH_TEMPERATURE = "th-temperature"; + + // 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_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_LOCATION = "location"; + public static final String CHANNEL_LAST_SEEN = "last-seen"; + 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_TIMEUTC = "timestamp"; + 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_CONNECTED_BOILER = "connected"; + public static final String CHANNEL_LAST_BILAN = "last-bilan"; + 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_THERM_ORIENTATION = "orientation"; + 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_HOME_CITY = "city"; + public static final String CHANNEL_HOME_COUNTRY = "country"; + public static final String CHANNEL_HOME_TIMEZONE = "timezone"; + public static final String CHANNEL_HOME_PERSONCOUNT = "person-count"; + public static final String CHANNEL_HOME_UNKNOWNCOUNT = "unknown-count"; + public static final String CHANNEL_CAMERA_IS_MONITORING = "is-monitoring"; + public static final String CHANNEL_CAMERA_SDSTATUS = "sd-status"; + public static final String CHANNEL_CAMERA_ALIMSTATUS = "alim-status"; + public static final String CHANNEL_CAMERA_LIVEPICTURE = "live-picture"; + public static final String CHANNEL_CAMERA_LIVEPICTURE_URL = "live-picture-url"; + public static final String CHANNEL_CAMERA_LIVESTREAM_URL = "live-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_URL = "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"; // 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 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 SUPPORTED_THING_TYPES_UIDS = Stream - .concat(SUPPORTED_DEVICE_THING_TYPES_UIDS.stream(), Stream.of(APIBRIDGE_THING_TYPE)) - .collect(Collectors.toSet()); + public static final String CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE = "auto-mode"; + public static final String CHANNEL_CAMERA_FLOODLIGHT = "floodlight"; - public static final Set HOME_EVENTS = Stream.of(EventTypeEnum.PERSON_AWAY) - .collect(Collectors.toSet()); - public static final Set 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 PERSON_EVENTS = Stream.of(EventTypeEnum.PERSON, EventTypeEnum.PERSON_AWAY) - .collect(Collectors.toSet()); - public static final Set PRESENCE_EVENTS = Stream - .of(EventTypeEnum.OUTDOOR, EventTypeEnum.ALIM, EventTypeEnum.DAILY_SUMMARY).collect(Collectors.toSet()); + // URI for the EventServlet + public static final String NETATMO_CALLBACK_URI = "/netatmo"; } 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/NetatmoDescriptionProvider.java similarity index 53% rename from bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NATherm1StateDescriptionProvider.java rename to bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoDescriptionProvider.java index 2c96cb43de1c1..6c7923325508e 100644 --- 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/NetatmoDescriptionProvider.java @@ -13,31 +13,24 @@ 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. + * Dynamic provider of state options while leaving other state description fields as original. * - * @author Gregory Moyer - Initial contribution - * @author Gaël L'hopital - Ported as-is in Netatmo binding + * @author Gaël L'hopital - Initial contribution */ -@Component(service = { DynamicStateDescriptionProvider.class, NATherm1StateDescriptionProvider.class }) +@Component(service = { DynamicStateDescriptionProvider.class, NetatmoDescriptionProvider.class }) @NonNullByDefault -public class NATherm1StateDescriptionProvider extends BaseDynamicStateDescriptionProvider { - +public class NetatmoDescriptionProvider extends BaseDynamicStateDescriptionProvider { @Activate - public NATherm1StateDescriptionProvider(final @Reference EventPublisher eventPublisher, // - final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // - final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { - this.eventPublisher = eventPublisher; - this.itemChannelLinkRegistry = itemChannelLinkRegistry; + public NetatmoDescriptionProvider( + @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoHandlerFactory.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoHandlerFactory.java index 2e1d0f1b65f95..2a6f977a108d5 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoHandlerFactory.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoHandlerFactory.java @@ -12,45 +12,31 @@ */ package org.openhab.binding.netatmo.internal; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.SERVICE_PID; -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; - -import javax.servlet.http.HttpServlet; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; 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.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.ModuleType; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.SignalHelper; +import org.openhab.binding.netatmo.internal.handler.HomeSecurityHandler; +import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet; import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.i18n.TranslationProvider; 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.framework.Constants; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -65,134 +51,78 @@ * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault -@Component(service = ThingHandlerFactory.class, configurationPid = "binding.netatmo") +@Component(service = ThingHandlerFactory.class, configurationPid = "binding.netatmo", property = Constants.SERVICE_PID + + "=" + SERVICE_PID) public class NetatmoHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(NetatmoHandlerFactory.class); - private final Map> discoveryServiceRegs = new HashMap<>(); - private final Map> webHookServiceRegs = new HashMap<>(); - 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 NetatmoDescriptionProvider stateDescriptionProvider; + private final ApiBridge apiBridge; + private final NetatmoServlet webhookServlet; @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; - this.stateDescriptionProvider = stateDescriptionProvider; + public NetatmoHandlerFactory(@Reference HttpService httpService, + @Reference NetatmoDescriptionProvider stateDescriptionProvider, + @Reference TimeZoneProvider timeZoneProvider, @Reference ApiBridge apiBridge, + @Reference NetatmoServlet webhookServlet) { + this.webhookServlet = webhookServlet; this.timeZoneProvider = timeZoneProvider; - this.localeProvider = localeProvider; - this.translationProvider = translationProvider; - } - - @Override - protected void activate(ComponentContext componentContext) { - super.activate(componentContext); - Dictionary properties = componentContext.getProperties(); - Object property = properties.get("backgroundDiscovery"); - if (property instanceof Boolean) { - backgroundDiscovery = ((Boolean) property).booleanValue(); - } else { - backgroundDiscovery = false; - } - logger.debug("backgroundDiscovery {}", backgroundDiscovery); + this.stateDescriptionProvider = stateDescriptionProvider; + this.apiBridge = apiBridge; } @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)); + for (ModuleType moduleType : ModuleType.values()) { + if (moduleType.matches(thingTypeUID)) { + return true; + } + } + return false; } @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; - } - } - - @Override - protected void removeHandler(ThingHandler thingHandler) { - if (thingHandler instanceof NetatmoBridgeHandler) { - ThingUID thingUID = thingHandler.getThing().getUID(); - unregisterDeviceDiscoveryService(thingUID); - unregisterWebHookServlet(thingUID); - } - } - - private synchronized void registerDeviceDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler) { - if (bundleContext != null) { - NetatmoModuleDiscoveryService discoveryService = new NetatmoModuleDiscoveryService(netatmoBridgeHandler, - localeProvider, translationProvider); - Map 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(); + Bridge bridge = (Bridge) thing; + BaseThingHandler handler; + for (ModuleType mt : ModuleType.values()) { + if (mt.matches(thingTypeUID)) { + handler = build(bridge, mt); + if (handler instanceof HomeSecurityHandler) { + ((HomeSecurityHandler) handler).setWebHookServlet(webhookServlet); + } + return handler; } } + logger.warn("ThingHandler not found for {}", thing.getThingTypeUID()); + return null; } - 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<>())); + public @Nullable BaseThingHandler build(Bridge bridge, ModuleType moduleType) { + List helpers = new ArrayList<>(); + if (moduleType.getSignalLevels() != NetatmoConstants.NO_RADIO) { + helpers.add(new SignalHelper(bridge, timeZoneProvider, moduleType.getSignalLevels())); } - return servlet; - } - - private synchronized void unregisterWebHookServlet(ThingUID thingUID) { - ServiceRegistration serviceReg = webHookServiceRegs.remove(thingUID); - if (serviceReg != null) { - serviceReg.unregister(); + try { + Constructor handlerConstructor = moduleType.getHandlerConstructor(); + if (handlerConstructor != null) { + for (Class helperClass : moduleType.getChannelHelpers()) { + Constructor helperConstructor = helperClass.getConstructor(Thing.class, TimeZoneProvider.class); + AbstractChannelHelper helper = (AbstractChannelHelper) helperConstructor + .newInstance(new Object[] { bridge, timeZoneProvider }); + if (helper != null) { + helpers.add(helper); + } + } + return (BaseThingHandler) handlerConstructor + .newInstance(new Object[] { bridge, helpers, apiBridge, stateDescriptionProvider }); + } + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + logger.warn("Error creating calling constructor : {}", e.getMessage()); } + return null; } } 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 index 0000000000000..96c3beb02bc2a --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/action/RoomActions.java @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2010-2021 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.handler.RoomHandler; +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 @Nullable RoomHandler handler; + + public RoomActions() { + logger.trace("Netatmo room actions service created"); + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + if (handler instanceof RoomHandler) { + this.handler = (RoomHandler) handler; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return handler; + } + + /** + * The setRoomThermpoint room thing action + */ + @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc") + public void setRoomThermpoint( + @ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp, + @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) { + setRoomThermpoint(temp, endTime, null); + } + + @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc") + public void setRoomThermpoint( + @ActionInput(name = "mode", label = "@text/actionInputModeLabel", description = "@text/actionInputModeDesc") @Nullable String mode, + @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) { + setRoomThermpoint(null, endTime, mode); + } + + @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc") + public void setRoomThermpoint( + @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) { + RoomHandler roomHandler = handler; + if (roomHandler == null) { + logger.debug("Handler not set for room thing actions."); + return; + } + + String targetMode = mode; + Long targetEndTime = endTime; + Double targetTemp = temp; + if (targetMode != null) { + if (!(targetMode.equals(SetpointMode.MAX.toString()) || targetMode.equals(SetpointMode.HOME.toString()) + || targetMode.equals(SetpointMode.MANUAL.toString()))) { + logger.debug("Mode can only be MAX, HOME or MANUAL for a room"); + return; + } + } + if (temp != null) { + logger.debug("Temperature provided, mode forced to MANUAL."); + targetMode = SetpointMode.MANUAL.toString(); + if (targetEndTime == null) { + logger.debug("Temperature provided but no endtime given, action ignored"); + return; + } + } else { + if (targetMode == null) { + logger.debug("mode is required if no temperature setpoint provided"); + return; + } else if (targetMode.equalsIgnoreCase(SetpointMode.HOME.toString())) { + targetEndTime = 0L; + targetTemp = 0.0; + } + } + + try { + roomHandler.thingActionCallSetRoomThermTemp(targetTemp != null ? targetTemp : 0, + targetEndTime != null ? targetEndTime : 0L, SetpointMode.valueOf(targetMode)); + } catch (IllegalArgumentException e) { + logger.debug("Ignoring setRoomThermpoint command due to illegal argument exception: {}", e.getMessage()); + } + } + + /** + * Static setLevel method for Rules DSL backward compatibility + */ + public static void setRoomThermpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime, + @Nullable String mode) { + ((RoomActions) actions).setRoomThermpoint(temp, endTime, mode); + } + + public static void setRoomThermpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime) { + setRoomThermpoint(actions, temp, endTime, null); + } + + public static void setRoomThermpoint(ThingActions actions, @Nullable String mode, @Nullable Long endTime) { + setRoomThermpoint(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 index 0000000000000..1fa9887688c04 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AircareApi.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010-2021 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.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.WeatherApi.NAStationDataResponse; +import org.openhab.binding.netatmo.internal.api.dto.NADeviceDataBody; +import org.openhab.binding.netatmo.internal.api.dto.NAMain; + +/** + * Base class for all Air Care related rest manager + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class AircareApi extends RestManager { + + public AircareApi(ApiBridge apiClient) { + super(apiClient, NetatmoConstants.AIR_QUALITY_SCOPES); + } + + /** + * + * The method gethomecoachsdata Returns data from a user Healthy Home Coach Station (measures and device specific + * data). + * + * @param deviceId Id of the device you want to retrieve information of (optional) + * @return NAStationDataResponse + * @throws NetatmoException If fail to call the API, e.g. server error or cannot deserialize the + * response body + */ + private NAStationDataResponse getHomeCoachData2(@Nullable String deviceId) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_HOMECOACH_SPATH); + if (deviceId != null) { + uriBuilder.queryParam(NA_DEVICEID_PARAM, deviceId); + } + NAStationDataResponse response = get(uriBuilder, NAStationDataResponse.class); + return response; + } + + public NAMain getHomeCoachData(String deviceId) throws NetatmoException { + NADeviceDataBody answer = getHomeCoachData2(deviceId).getBody(); + NAMain station = answer.getDevice(deviceId); + if (station != null) { + return station; + } + throw new NetatmoException(String.format("Unexpected answer cherching device '%s' : not found.", deviceId)); + } + + public NADeviceDataBody getHomeCoachDataBody(@Nullable String deviceId) throws NetatmoException { + return getHomeCoachData2(deviceId).getBody(); + // NAMain result = answer.getDevice(deviceId); + // if (result != null) { + // return result; + // } + // throw new NetatmoException(String.format("Unexpected answer searching device '%s' : not found.", deviceId)); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiBridge.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiBridge.java new file mode 100644 index 0000000000000..24f8b727dbb8f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiBridge.java @@ -0,0 +1,280 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoBindingConstants.SERVICE_PID; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +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.openhab.binding.netatmo.internal.api.NetatmoConstants.Scope; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.config.NetatmoBindingConfiguration; +import org.openhab.binding.netatmo.internal.deserialization.NADynamicObjectMap; +import org.openhab.binding.netatmo.internal.deserialization.NADynamicObjectMapDeserializer; +import org.openhab.binding.netatmo.internal.deserialization.NAHomeDeserializer; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMapDeserializer; +import org.openhab.binding.netatmo.internal.deserialization.NAPushType; +import org.openhab.binding.netatmo.internal.deserialization.NAPushTypeDeserializer; +import org.openhab.binding.netatmo.internal.deserialization.StrictEnumTypeAdapterFactory; +import org.openhab.binding.netatmo.internal.utils.BindingUtils; +import org.openhab.core.auth.client.oauth2.OAuthFactory; +import org.openhab.core.common.ThreadPoolManager; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.OpenClosedType; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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 ApiBridge} allows the communication to the various Husqvarna rest apis like the + * AutomowerConnectApi or the AuthenticationApi + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +@Component(service = ApiBridge.class, configurationPid = "binding.netatmo") +public class ApiBridge { + private static final int TIMEOUT_MS = 10000; + + private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_PID); + private final Logger logger = LoggerFactory.getLogger(ApiBridge.class); + private final List listeners = new ArrayList<>(); + private final Map httpHeaders = new HashMap<>(); + + private final HttpClient httpClient; + private final AuthenticationApi connectApi; + private final Gson gson; + + private NetatmoBindingConfiguration configuration = new NetatmoBindingConfiguration(); + private Map, Object> managers = new HashMap<>(); + private ConnectionStatus connectionStatus = ConnectionStatus.Unknown(); + private List grantedScopes = List.of(); + + @Activate + public ApiBridge(@Reference OAuthFactory oAuthFactory, @Reference HttpClientFactory httpClientFactory, + @Reference TimeZoneProvider timeZoneProvider, ComponentContext componentContext) { + this.httpClient = httpClientFactory.getCommonHttpClient(); + this.httpHeaders.put(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded;charset=UTF-8"); + this.connectApi = new AuthenticationApi(this, oAuthFactory, configuration, scheduler); + + gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()) + .registerTypeAdapter(NAObjectMap.class, new NAObjectMapDeserializer()) + .registerTypeAdapter(NADynamicObjectMap.class, new NADynamicObjectMapDeserializer()) + .registerTypeAdapter(NAHome.class, new NAHomeDeserializer()) + .registerTypeAdapter(NAPushType.class, new NAPushTypeDeserializer()) + .registerTypeAdapter(ZonedDateTime.class, + (JsonDeserializer) (json, type, jsonDeserializationContext) -> { + long netatmoTS = json.getAsJsonPrimitive().getAsLong(); + Instant i = Instant.ofEpochSecond(netatmoTS); + return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone()); + }) + .registerTypeAdapter(OnOffType.class, + (JsonDeserializer) (json, type, jsonDeserializationContext) -> OnOffType + .from(json.getAsJsonPrimitive().getAsString())) + .registerTypeAdapter(OpenClosedType.class, + (JsonDeserializer) (json, type, jsonDeserializationContext) -> { + String value = json.getAsJsonPrimitive().getAsString().toUpperCase(); + return "TRUE".equals(value) || "1".equals(value) ? OpenClosedType.CLOSED + : OpenClosedType.OPEN; + }) + .create(); + + modified(BindingUtils.ComponentContextToMap(componentContext)); + } + + @Modified + protected void modified(Map config) { + configuration.update(new Configuration(config).as(NetatmoBindingConfiguration.class)); + logger.debug("Updated binding configuration to {}", configuration); + openConnection(); + } + + void openConnection() { + try { + configuration.checkIfValid(); + connectApi.authenticate(); + setConnectionStatus(ConnectionStatus.Success()); + } catch (NetatmoException e) { + setConnectionStatus(ConnectionStatus.Failed("Will retry to connect Netatmo API, this one failed : %s", e)); + scheduler.schedule(() -> openConnection(), configuration.reconnectInterval, TimeUnit.SECONDS); + } + } + + private void setConnectionStatus(ConnectionStatus status) { + connectionStatus = status; + if (!connectionStatus.isConnected()) { + onAccessTokenResponse("", List.of()); + } + listeners.forEach(listener -> listener.notifyStatusChange(status)); + } + + @SuppressWarnings("unchecked") + public @Nullable T getRestManager(Class typeOfRest) { + if (!managers.containsKey(typeOfRest)) { + try { + Constructor constructor = typeOfRest.getConstructor(ApiBridge.class); + T tentative = (T) constructor.newInstance(new Object[] { this }); + if (grantedScopes.containsAll(tentative.getRequiredScopes())) { + managers.put(typeOfRest, tentative); + } else { + logger.warn("Required scopes missing to access {}", typeOfRest); + } + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + logger.error("Error invoking RestManager constructor for class {} : {}", typeOfRest, e.getMessage()); + } + + } + return (T) managers.get(typeOfRest); + } + + public Optional getWeatherApi() { + return Optional.ofNullable(getRestManager(WeatherApi.class)); + } + + public Optional getEnergyApi() { + return Optional.ofNullable(getRestManager(EnergyApi.class)); + } + + public Optional getAirCareApi() { + return Optional.ofNullable(getRestManager(AircareApi.class)); + } + + public Optional getSecurityApi() { + return Optional.ofNullable(getRestManager(SecurityApi.class)); + } + + public HomeApi getHomeApi() { + HomeApi homeApi = (HomeApi) managers.get(HomeApi.class); + if (homeApi == null) { + homeApi = new HomeApi(this); + managers.put(HomeApi.class, homeApi); + } + return homeApi; + } + + synchronized T executeUri(URI uri, HttpMethod method, Class classOfT, @Nullable String payload) + throws NetatmoException { + // String url = anUrl.startsWith("http") ? anUrl + // : (baseUrl ? NetatmoConstants.NA_API_URL : NetatmoConstants.NA_APP_URL) + anUrl; + try { + String url = uri.toURL().toString(); + + logger.debug("executeUrl {} {} ", method.toString(), url); + + final Request request = httpClient.newRequest(url).method(method).timeout(TIMEOUT_MS, + TimeUnit.MILLISECONDS); + + httpHeaders.entrySet().forEach(entry -> request.header(entry.getKey(), entry.getValue())); + + if (payload != null && (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method))) { + InputStream stream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8)); + try (final InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider( + stream)) { + request.content(inputStreamContentProvider, null); + } + if (!NetatmoConstants.NA_API_URL.contains(uri.getHost())) { + request.getHeaders().remove(HttpHeader.CONTENT_TYPE); + request.header(HttpHeader.CONTENT_TYPE, "application/json;charset=utf-8"); + } + } + + ContentResponse response = request.send(); + + int statusCode = response.getStatus(); + if (statusCode >= 200 && statusCode < 300) { + String responseBody = new String(response.getContent(), StandardCharsets.UTF_8); + T deserialized = deserialize(classOfT, responseBody); + return deserialized; + } + + switch (statusCode) { + case HttpStatus.NOT_FOUND_404: + throw new NetatmoException(statusCode, "Target '" + response.getRequest().getURI() + + "' seems unavailable : " + response.getContentAsString()); + case HttpStatus.FORBIDDEN_403: + case HttpStatus.UNAUTHORIZED_401: + throw new NetatmoException(statusCode, + "Authorization exception : " + response.getContentAsString()); + default: + throw new NetatmoException(statusCode, response.getContentAsString()); + } + } catch (InterruptedException | TimeoutException | ExecutionException | MalformedURLException e) { + throw new NetatmoException("Exception while calling " + uri.toString(), e); + } + } + + public T deserialize(Class classOfT, String serviceAnswer) throws NetatmoException { + try { + T deserialized = gson.fromJson(serviceAnswer, classOfT); + return deserialized; + } catch (JsonSyntaxException e) { + throw new NetatmoException(String.format("Unexpected error deserializing '%s'", serviceAnswer), e); + } + } + + public void onAccessTokenResponse(String accessToken, List grantedScopes) { + this.grantedScopes = grantedScopes; + httpHeaders.put(HttpHeader.AUTHORIZATION, "Bearer " + accessToken); + } + + public void addConnectionListener(ConnectionListener listener) { + listeners.add(listener); + listener.notifyStatusChange(connectionStatus); + } + + public void removeConnectionListener(ConnectionListener listener) { + listeners.remove(listener); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiOkResponse.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiOkResponse.java new file mode 100644 index 0000000000000..a04889fac9f6d --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiOkResponse.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * The {@link ApiResponse} models a response that only holds a status + * toward the request sent to the API + * + * @author Gaël L'hopital - Initial contribution + */ +public class ApiOkResponse extends ApiResponse { + public boolean isSuccess() { + return "ok".equals(getStatus()); + } +} 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 index 0000000000000..890233c32910e --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiResponse.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * The {@link ApiResponse} models a response returned by API call + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class ApiResponse { + private @NonNullByDefault({}) String status; + private T body; + + public String getStatus() { + return status; + } + + public 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 index 0000000000000..0ca49ed72c7d3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.ALL_SCOPES; +import static org.openhab.core.auth.oauth2client.internal.Keyword.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +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.dto.NAAccessTokenResponse; +import org.openhab.binding.netatmo.internal.config.NetatmoBindingConfiguration; +import org.openhab.core.auth.client.oauth2.OAuthFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Allows access to the AutomowerConnectApi + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +class AuthenticationApi extends RestManager { + private static final String SCOPES = ALL_SCOPES.stream().map(s -> s.name().toLowerCase()) + .collect(Collectors.joining(" ")); + + private final Logger logger = LoggerFactory.getLogger(AuthenticationApi.class); + private final NetatmoBindingConfiguration configuration; + private final ScheduledExecutorService scheduler; + + AuthenticationApi(ApiBridge apiClient, OAuthFactory oAuthFactory, NetatmoBindingConfiguration configuration, + ScheduledExecutorService scheduler) { + super(apiClient); + this.configuration = configuration; + this.scheduler = scheduler; + } + + void authenticate() throws NetatmoException { + Map payload = new HashMap<>(); + payload.put(PASSWORD, configuration.password); + payload.put(USERNAME, configuration.username); + payload.put(SCOPE, SCOPES); + requestToken(getPayload(PASSWORD, payload)); + } + + private void requestToken(String tokenRequest) throws NetatmoException { + NAAccessTokenResponse answer = apiHandler.executeUri(getOAuthUri(), HttpMethod.POST, + NAAccessTokenResponse.class, tokenRequest); + apiHandler.onAccessTokenResponse(answer.getAccessToken(), answer.getScope()); + scheduler.schedule(() -> { + try { + requestToken(getPayload(REFRESH_TOKEN, Map.of(REFRESH_TOKEN, answer.getRefreshToken()))); + } catch (NetatmoException e) { + logger.warn("Unable to refresh access token : {}, trying to reopen connection.", e.getMessage()); + apiHandler.openConnection(); + } + }, Math.round(answer.getExpiresIn() * 0.8), TimeUnit.SECONDS); + } + + private String getPayload(String grantType, Map entries) { + Map payload = new HashMap<>(entries); + payload.put(GRANT_TYPE, grantType); + payload.put(CLIENT_ID, configuration.clientId); + payload.put(CLIENT_SECRET, configuration.clientSecret); + return payload.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ConnectionListener.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ConnectionListener.java new file mode 100644 index 0000000000000..fac18577ca16a --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ConnectionListener.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public interface ConnectionListener { + + void notifyStatusChange(ConnectionStatus connectionStatus); +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ConnectionStatus.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ConnectionStatus.java new file mode 100644 index 0000000000000..1697dffb46c9d --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ConnectionStatus.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class ConnectionStatus { + private final boolean isConnected; + private final String message; + + private ConnectionStatus(boolean isConnected, String message) { + this.isConnected = isConnected; + this.message = message; + } + + public static ConnectionStatus Success() { + return new ConnectionStatus(true, "Successfully connected"); + } + + public static ConnectionStatus Failed(String message) { + return new ConnectionStatus(false, message); + } + + public static ConnectionStatus Failed(String format, Exception e) { + return new ConnectionStatus(false, String.format(format, e.getMessage())); + } + + public static ConnectionStatus Unknown() { + return new ConnectionStatus(false, "No connection tried yet."); + } + + public boolean isConnected() { + return isConnected; + } + + public String getMessage() { + return message; + } +} 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 index 0000000000000..da83a3b7be160 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EnergyApi.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class EnergyApi extends RestManager { + // private class NAThermostatDataResponse extends ApiResponse> { + // } + + public class NAHomeStatusResponse extends ApiResponse { + } + + public EnergyApi(ApiBridge apiClient) { + super(apiClient, NetatmoConstants.ENERGY_SCOPES); + } + + public NAHome getHomeStatus(String homeId) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_HOMESTATUS_SPATH).queryParam(NA_HOMEID_PARAM, homeId) + // TODO : @mdillman : can't this be wrapped in a single queryParam with multiple moduleTypes ? + .queryParam("device_types", ModuleType.NAPlug.name()).queryParam("device_types", ModuleType.NRV.name()) + .queryParam("device_types", ModuleType.NATherm1.name()); + + NAHomeStatusResponse response = get(uriBuilder, NAHomeStatusResponse.class); + return response.getBody().getHome(); + } + + // public NAPlug getThermostatData(String equipmentId) throws NetatmoException { + // UriBuilder uriBuilder = getApiUriBuilder().path(NA_GETTHERMOSTAT_SPATH); + // uriBuilder.queryParam(NA_DEVICEID_PARAM, equipmentId); + // NADeviceDataBody answer = get(uriBuilder.build(), NAThermostatDataResponse.class).getBody(); + // + // NAPlug plug = answer.getDevice(equipmentId); + // if (plug != null) { + // return plug; + // } + // throw new NetatmoException(String.format("Unexpected answer cherching device '%s' : not found.", equipmentId)); + // } + + /** + * + * 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) + * @return boolean success + * @throws NetatmoException If fail to call the API, e.g. server error or cannot deserialize the + * response body + */ + public boolean switchSchedule(String homeId, String scheduleId) throws NetatmoException { + UriBuilder uriBuilder = getAppUriBuilder().path(NA_SWITCHSCHEDULE_SPATH); + String payload = String.format("{\"home_id\":\"%s\",\"schedule_id\":\"%s\"}", homeId, scheduleId); + post(uriBuilder, ApiOkResponse.class, payload); + return true; + } + + /** + * + * 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) + * @return boolean success + * @throws NetatmoException If fail to call the API, e.g. server error or cannot deserialize the + * response body + */ + public boolean setThermMode(String homeId, String mode) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_SETTHERMMODE_SPATH); + uriBuilder.queryParam(NA_HOMEID_PARAM, homeId).queryParam("mode", mode); + post(uriBuilder, ApiOkResponse.class, null); + return true; + } + + /** + * + * The method setroomthermpoint 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 When using the manual or max setpoint_mode, this parameter defines when the setpoint + * expires. (optional) + * @param temp When using the manual setpoint_mode, this parameter defines the temperature setpoint (in + * Celcius) to use. (optional) + * @return ApiOkResponse + * @throws NetatmoCommunicationException If fail to call the API, e.g. server error or cannot deserialize the + * response body + */ + public boolean setRoomThermpoint(String homeId, String roomId, SetpointMode mode, long endtime, double temp) + throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_SETROOMTHERMPOINT_SPATH).queryParam(NA_HOMEID_PARAM, roomId) + .queryParam(NA_ROOMID_PARAM, mode.getDescriptor()); + if (mode == SetpointMode.MANUAL || mode == SetpointMode.MAX) { + uriBuilder.queryParam("endtime", endtime); + if (mode == SetpointMode.MANUAL) { + uriBuilder.queryParam("temp", temp); + } + } + post(uriBuilder, ApiOkResponse.class, null); + return true; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EventSubType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EventSubType.java new file mode 100644 index 0000000000000..7477333d6a5b8 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EventSubType.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * This enum describes sub events in relation to a given event + * according to API documentation + * + * @author Gaël L'hopital - Initial contribution + */ +public enum EventSubType { + MISSING_SD(EventType.SD, 1), // Missing SD Card + SD_INSERTED(EventType.SD, 2), // SD Card inserted + SD_FORMATED(EventType.SD, 3), // SD Card formated + WORKING_SD(EventType.SD, 4), // Working SD Card + DEFECTIVE_SD(EventType.SD, 5), // Defective SD Card + INCOMPATIBLE_SD_SPEED(EventType.SD, 6), // Incompatible SD Card speed + INSUFFICIENT_SD_SPACE(EventType.SD, 7), // Insufficient SD Card space + INCORRECT_POWER(EventType.ALIM, 1), // incorrect power adapter + CORRECT_POWER(EventType.ALIM, 2), // correct power adapter + + // Artificially implemented by the binding subtypes + ARRIVAL(EventType.PERSON, 1), // Person arrived + HUMAN(EventType.MOVEMENT, 1), // Human seen + VEHICLE(EventType.MOVEMENT, 2), // Car seen + ANIMAL(EventType.MOVEMENT, 3); // Animal seen + + // Left for future implementation + // SOUNDING_STOPPED(EventType.SIREN_SOUNDING, 0), + // SOUNDING_STARTED(EventType.SIREN_SOUNDING, 1), + // + // CHAMBER_CLEAN(EventType.DETECTION_CHAMBER_STATUS, 0), + // CHAMBER_DUSTY(EventType.DETECTION_CHAMBER_STATUS, 1), + // + // SOUND_TEST_OK(EventType.SOUND_TEST, 0), + // SOUND_TEST_ERROR(EventType.SOUND_TEST, 1), + // + // BATTERY_VERY_LOW(EventType.BATTERY_STATUS, 1), + // + // TAMPERED_READY(EventType.TAMPERED, 0), + // TAMPERED_TAMPERED(EventType.TAMPERED, 1), + // + // SMOKE_CLEARED(EventType.SMOKE, 0), + // SMOKE_DETECTED(EventType.SMOKE, 1), + // + // WIFI_ERROR(EventType.WIFI_STATUS, 0), + // WIFI_OK(EventType.WIFI_STATUS, 1); + + EventType type; + int subType; + + EventSubType(EventType sd, int i) { + this.type = sd; + this.subType = i; + } + + public EventType getType() { + return type; + } + + public int getSubType() { + return subType; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EventType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EventType.java new file mode 100644 index 0000000000000..9d5af94ed67f4 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EventType.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2010-2021 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.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(Set.of()), + @SerializedName("person") // When the Indoor Camera detects a face + PERSON(Set.of(ModuleType.NAPerson, ModuleType.NACamera)), + + @SerializedName("person_away") // When geofencing indicates that the person has left the home + PERSON_AWAY(Set.of(ModuleType.NAPerson)), + + @SerializedName("outdoor") // When the Outdoor Camera detects a human, a car or an animal + OUTDOOR(Set.of(ModuleType.NOC)), + + @SerializedName("movement") // When the Indoor Camera detects motion + MOVEMENT(Set.of(ModuleType.NACamera)), + + @SerializedName("new_module") // A new Module has been paired with the Indoor Camera + NEW_MODULE(Set.of(ModuleType.NACamera)), + + @SerializedName("module_connect") // Module is connected with the Indoor Camera + MODULE_CONNECT(Set.of(ModuleType.NACamera)), + + @SerializedName("module_disconnect") // Module lost its connection with the Indoor Camera + MODULE_DISCONNECT(Set.of(ModuleType.NACamera)), + + @SerializedName("module_low_battery") // Module's battery is low + MODULE_LOW_BATTERY(Set.of(ModuleType.NACamera)), + + @SerializedName("module_end_update") // Module's firmware update is over + MODULE_END_UPDATE(Set.of(ModuleType.NACamera)), + + @SerializedName("connection") // When the Camera connects to Netatmo servers + CONNECTION(Set.of(ModuleType.NACamera, ModuleType.NOC)), + + @SerializedName("disconnection") // When the Camera loses connection with Netatmo servers + DISCONNECTION(Set.of(ModuleType.NACamera, ModuleType.NOC)), + + @SerializedName("on") // When Camera Monitoring is resumed + ON(Set.of(ModuleType.NACamera, ModuleType.NOC)), + + @SerializedName("off") // When Camera Monitoring is turned off + OFF(Set.of(ModuleType.NACamera, ModuleType.NOC)), + + @SerializedName("boot") // When the Camera is booting + BOOT(Set.of(ModuleType.NACamera, ModuleType.NOC)), + + @SerializedName("sd") // When Camera SD Card status changes + SD(Set.of(ModuleType.NACamera, ModuleType.NOC)), + + @SerializedName("alim") // When Camera power supply status changes + ALIM(Set.of(ModuleType.NACamera, ModuleType.NOC)); + + // Left for future implementation + // @SerializedName("tag_big_move") // When a Smart Sensor detects a big move + // TAG_BIG_MOVE(Set.of(ModuleType.NACamera, ModuleType.NACamDoorTag)), + // + // @SerializedName("tag_small_move") + // TAG_SMALL_MOVE(Set.of(ModuleType.NACamera, ModuleType.NACamDoorTag)), + // + // @SerializedName("tag_uninstalled") + // TAG_UNINSTALLED(Set.of(ModuleType.NACamera, ModuleType.NACamDoorTag)), + // + // @SerializedName("tag_open") + // TAG_OPEN(Set.of(ModuleType.NACamera, ModuleType.NACamDoorTag)), + // + // @SerializedName("hush") + // HUSH(Set.of(ModuleType.NSD)), + // + // @SerializedName("smoke") + // SMOKE(Set.of(ModuleType.NSD)), + // + // @SerializedName("tampered") + // TAMPERED(Set.of(ModuleType.NSD)), + // + // @SerializedName("wifi_status") + // WIFI_STATUS(Set.of(ModuleType.NSD)), + // + // @SerializedName("battery_status") + // BATTERY_STATUS(Set.of(ModuleType.NSD)), + // + // @SerializedName("detection_chamber_status") + // DETECTION_CHAMBER_STATUS(Set.of(ModuleType.NSD)), + // + // @SerializedName("sound_test") + // SOUND_TEST(Set.of(ModuleType.NSD)), + // + // @SerializedName("siren_sounding") + // SIREN_SOUNDING(Set.of(ModuleType.NIS)), + // + // @SerializedName("siren_tampered") + // SIREN_TAMPERED(Set.of(ModuleType.NIS)); + + private Set appliesTo; + + EventType(Set appliesTo) { + this.appliesTo = 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/HomeApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/HomeApi.java new file mode 100644 index 0000000000000..3f779af13772f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/HomeApi.java @@ -0,0 +1,144 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.*; + +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.NetatmoConstants.PresenceLightMode; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeData; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeSecurity; +import org.openhab.binding.netatmo.internal.api.dto.NAPing; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class HomeApi extends RestManager { + + public HomeApi(ApiBridge apiClient) { + super(apiClient); + } + + public class NAHomesDataResponse extends ApiResponse { + } + + private class NAHomeDataResponse extends ApiResponse { + } + + public List getHomeData(String homeId) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_GETHOME_SPATH); + uriBuilder.queryParam(NA_MODULEID_PARAM, homeId); + return get(uriBuilder, NAHomeDataResponse.class).getBody().getHomes(); + } + + public List getHomes(@Nullable String homeId) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_GETHOME_SPATH); + NAHomeData response = get(uriBuilder, NAHomeDataResponse.class).getBody(); + List homeListResponse = getHomeList(null, null); + List homeDataResponse = response.getHomes(); + homeListResponse.forEach(h1 -> { + homeDataResponse.stream().filter(h2 -> h2.getId().equals(h1.getId())).findFirst().ifPresent(h2 -> { + h1.setPlace(h2.getPlace()); + h1.getModules().putAll(h2.getModules()); + if (h1 instanceof NAHomeSecurity && h2 instanceof NAHomeSecurity) { + ((NAHomeSecurity) h1).getCameras().putAll(((NAHomeSecurity) h2).getCameras()); + ((NAHomeSecurity) h1).getPersons().putAll(((NAHomeSecurity) h2).getPersons()); + } + }); + }); + // Complete gethomedata with informations provided by homesdata + + if (homeId != null) { + homeListResponse.removeIf(home -> !home.getId().equals(homeId)); + } + + return homeListResponse; + } + + public List getHomeList(@Nullable String homeId, @Nullable ModuleType type) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_HOMES_SPATH); + + if (homeId != null) { + uriBuilder.queryParam(NA_HOMEID_PARAM, homeId); + } + if (type != null) { + uriBuilder.queryParam("gateway_types", type.name()); + } + + NAHomesDataResponse response = get(uriBuilder, NAHomesDataResponse.class); + return response.getBody().getHomes(); + } + + public boolean setpersonsaway(String homeId, String personId) throws NetatmoException { + UriBuilder uriBuilder = getAppUriBuilder().path(NA_PERSON_AWAY_SPATH); + String payload = String.format("{\"home_id\":\"%s\",\"person_id\":\"%s\"}", homeId, personId); + post(uriBuilder, ApiOkResponse.class, payload); + return true; + } + + public boolean setpersonshome(String homeId, String personId) throws NetatmoException { + UriBuilder uriBuilder = getAppUriBuilder().path(NA_PERSON_HOME_SPATH); + String payload = String.format("{\"home_id\":\"%s\",\"person_ids\":[\"%s\"]}", homeId, personId); + post(uriBuilder, ApiOkResponse.class, payload); + return true; + } + + // public boolean switchSchedule(String homeId, String scheduleId) throws NetatmoException { + // String req = "switchschedule"; + // String payload = String.format("{\"home_id\":\"%s\",\"schedule_id\":\"%s\"}", homeId, scheduleId); + // ApiOkResponse response = post(req, payload, ApiOkResponse.class, false); + // if (!response.isSuccess()) { + // throw new NetatmoException(String.format("Unsuccessfull schedule change : %s", response.getStatus())); + // } + // return true; + // } + + public String ping(String vpnUrl) throws NetatmoException { + UriBuilder uriBuilder = UriBuilder.fromUri(vpnUrl).path(NA_COMMAND_PATH).path("ping"); + NAPing response = get(uriBuilder, NAPing.class); + return response.getStatus(); + } + + public boolean changeStatus(String localCameraURL, boolean isOn) throws NetatmoException { + UriBuilder uriBuilder = UriBuilder.fromUri(localCameraURL).path(NA_COMMAND_PATH).path("changestatus"); + uriBuilder.queryParam("status", isOn ? "on" : "off"); + // String url = localCameraURL + "/command/changestatus?status=" + (isOn ? "on" : "off"); + ApiOkResponse response = post(uriBuilder, ApiOkResponse.class, null); + if (!response.isSuccess()) { + throw new NetatmoException(String.format("Unsuccessfull camara status change : %s", response.getStatus())); + } + return true; + } + + public boolean changeFloodLightMode(String localCameraURL, PresenceLightMode mode) throws NetatmoException { + UriBuilder uriBuilder = UriBuilder.fromUri(localCameraURL).path(NA_COMMAND_PATH).path("floodlight_set_config"); + uriBuilder.queryParam("config", "%7B%22mode%22:%22" + mode.toString() + "%22%7D"); + // String url = localCameraURL + "/command/floodlight_set_config?config=%7B%22mode%22:%22" + mode.toString() + // + "%22%7D"; + ApiOkResponse response = get(uriBuilder, ApiOkResponse.class); + if (!response.isSuccess()) { + throw new NetatmoException(String.format("Unsuccessfull camara status change : %s", response.getStatus())); + } + return true; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ModuleType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ModuleType.java new file mode 100644 index 0000000000000..dec4aa3a02840 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ModuleType.java @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.api.NetatmoConstants.*; + +import java.lang.reflect.Constructor; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.dto.NAModule; +import org.openhab.binding.netatmo.internal.api.dto.NAPlug; +import org.openhab.binding.netatmo.internal.api.dto.NARoom; +import org.openhab.binding.netatmo.internal.api.dto.NAThermostat; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.api.dto.NAWelcome; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.BatteryHelper; +import org.openhab.binding.netatmo.internal.channelhelper.CameraChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.Co2ChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.DeviceChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.HomeCoachChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.HomeEnergyChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.HomeSecurityChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.HumidityChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.LocationChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.MeasuresChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.NoiseChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.PersonChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.PlugChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.PresenceChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.PressureChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.RainChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.RoomChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.RoomSetpointChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.RoomTempChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.TemperatureChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.Therm1PropsChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.Therm1SetpointChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.Therm1TempChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.WindChannelHelper; +import org.openhab.binding.netatmo.internal.handler.CameraHandler; +import org.openhab.binding.netatmo.internal.handler.HomeCoachHandler; +import org.openhab.binding.netatmo.internal.handler.HomeEnergyHandler; +import org.openhab.binding.netatmo.internal.handler.HomeSecurityHandler; +import org.openhab.binding.netatmo.internal.handler.MainHandler; +import org.openhab.binding.netatmo.internal.handler.NRVHandler; +import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler; +import org.openhab.binding.netatmo.internal.handler.PersonHandler; +import org.openhab.binding.netatmo.internal.handler.PlugHandler; +import org.openhab.binding.netatmo.internal.handler.PresenceHandler; +import org.openhab.binding.netatmo.internal.handler.RoomHandler; +import org.openhab.binding.netatmo.internal.handler.Therm1Handler; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingTypeUID; + +/** + * This enum all handled Netatmo modules and devices along with their capabilities + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public enum ModuleType { + UNKNOWN(null, RefreshPolicy.NONE, null, List.of(), List.of(), List.of(), null), + + // Security Group + NAHomeSecurity(HomeSecurityHandler.class, RefreshPolicy.CONFIG, null, List.of(), + List.of(HomeSecurityChannelHelper.class), List.of(GROUP_HOME_SECURITY), null), + NAPerson(PersonHandler.class, RefreshPolicy.PARENT, NAHomeSecurity, List.of(), List.of(PersonChannelHelper.class), + List.of(GROUP_PERSON, GROUP_PERSON_EVENT), null), + NACamera(CameraHandler.class, RefreshPolicy.PARENT, NAHomeSecurity, List.of(), List.of(CameraChannelHelper.class), + List.of(GROUP_WELCOME, GROUP_WELCOME_EVENT), NAWelcome.class), + NOC(PresenceHandler.class, RefreshPolicy.PARENT, NAHomeSecurity, List.of(), + List.of(CameraChannelHelper.class, PresenceChannelHelper.class), + List.of(GROUP_WELCOME, GROUP_WELCOME_EVENT, GROUP_PRESENCE), NAWelcome.class), + + // Weather group + NAHomeWeather(null, RefreshPolicy.NONE, null, List.of(), List.of(), List.of(), null), + NAMain(MainHandler.class, RefreshPolicy.AUTO, null, List.of("measure", "measure-timestamp"), + List.of(PressureChannelHelper.class, NoiseChannelHelper.class, HumidityChannelHelper.class, + TemperatureChannelHelper.class, Co2ChannelHelper.class, DeviceChannelHelper.class, + MeasuresChannelHelper.class, LocationChannelHelper.class), + List.of(GROUP_TEMPERATURE, GROUP_HUMIDITY, GROUP_CO2, GROUP_NOISE, GROUP_PRESSURE, GROUP_DEVICE, + GROUP_SIGNAL, GROUP_LOCATION), + NAThing.class), + NAModule1(NetatmoDeviceHandler.class, RefreshPolicy.PARENT, NAMain, List.of("measure", "measure-timestamp"), + List.of(HumidityChannelHelper.class, TemperatureChannelHelper.class, BatteryHelper.class, + MeasuresChannelHelper.class), + List.of(GROUP_TEMPERATURE, GROUP_HUMIDITY, GROUP_SIGNAL, GROUP_BATTERY), NAModule.class), + NAModule2(NetatmoDeviceHandler.class, RefreshPolicy.PARENT, NAMain, List.of(), + List.of(WindChannelHelper.class, BatteryHelper.class), List.of(GROUP_WIND, GROUP_SIGNAL, GROUP_BATTERY), + NAModule.class), + NAModule3(NetatmoDeviceHandler.class, RefreshPolicy.PARENT, NAMain, List.of("sum-rain"), + List.of(RainChannelHelper.class, BatteryHelper.class, MeasuresChannelHelper.class), + List.of(GROUP_RAIN, GROUP_SIGNAL, GROUP_BATTERY), NAModule.class), + NAModule4(NetatmoDeviceHandler.class, RefreshPolicy.PARENT, NAMain, List.of("measure", "measure-timestamp"), + List.of(HumidityChannelHelper.class, TemperatureChannelHelper.class, Co2ChannelHelper.class, + BatteryHelper.class, MeasuresChannelHelper.class), + List.of(GROUP_TEMPERATURE, GROUP_HUMIDITY, GROUP_CO2, GROUP_SIGNAL, GROUP_BATTERY), NAModule.class), + + // Aircare group + NHC(HomeCoachHandler.class, RefreshPolicy.AUTO, null, List.of("measure", "measure-timestamp"), + List.of(NoiseChannelHelper.class, HumidityChannelHelper.class, PressureChannelHelper.class, + TemperatureChannelHelper.class, Co2ChannelHelper.class, HomeCoachChannelHelper.class, + DeviceChannelHelper.class, MeasuresChannelHelper.class), + List.of(GROUP_HEALTH, GROUP_TEMPERATURE, GROUP_HUMIDITY, GROUP_PRESSURE, GROUP_CO2, GROUP_NOISE, + GROUP_DEVICE, GROUP_SIGNAL), + NAThing.class), + + // Energy group + NAHomeEnergy(HomeEnergyHandler.class, RefreshPolicy.AUTO, null, List.of(), List.of(HomeEnergyChannelHelper.class), + List.of(GROUP_HOME_ENERGY), null), + NAPlug(PlugHandler.class, RefreshPolicy.CONFIG, NAHomeEnergy, List.of(), + List.of(PlugChannelHelper.class, DeviceChannelHelper.class), + List.of(GROUP_PLUG, GROUP_DEVICE, GROUP_SIGNAL), NAPlug.class), + NATherm1(Therm1Handler.class, RefreshPolicy.PARENT, NAPlug, List.of(), + List.of(Therm1PropsChannelHelper.class, Therm1SetpointChannelHelper.class, Therm1TempChannelHelper.class, + BatteryHelper.class), + List.of(GROUP_TH_PROPERTIES, GROUP_TH_SETPOINT, GROUP_TH_TEMPERATURE, GROUP_SIGNAL, GROUP_BATTERY), + NAThermostat.class), + NARoom(RoomHandler.class, RefreshPolicy.CONFIG, NAHomeEnergy, List.of(), + List.of(RoomChannelHelper.class, RoomTempChannelHelper.class, RoomSetpointChannelHelper.class), + List.of(GROUP_ROOM_PROPERTIES, GROUP_TH_SETPOINT, GROUP_ROOM_TEMPERATURE), NARoom.class), + NRV(NRVHandler.class, RefreshPolicy.CONFIG, NAHomeEnergy, List.of(), List.of(BatteryHelper.class), + List.of(GROUP_ENERGY_BATTERY, GROUP_SIGNAL), NAPlug.class), + // Left for future implementation + // NACamDoorTag : self explaining + // NSD : smoke detector + // NIS : indoor siren + // NDB : doobell + ; + + public enum RefreshPolicy { + AUTO, + PARENT, + CONFIG, + NONE; + } + + private final List groups; + private final List extensions; + private final RefreshPolicy refreshPeriod; + private final @Nullable ThingTypeUID bridgeThingType; + private final @Nullable Class handlerClass; + private final @Nullable Class dto; + private final ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, this.name()); + private final List> channelHelpers; + + ModuleType(@Nullable Class handlerClass, RefreshPolicy refreshPeriod, @Nullable ModuleType bridge, + List extensions, List> setOfHelpers, List groups, + @Nullable Class dto) { + this.handlerClass = handlerClass; + this.groups = groups; + this.refreshPeriod = refreshPeriod; + this.extensions = extensions; + this.channelHelpers = setOfHelpers; + this.bridgeThingType = bridge != null ? bridge.thingTypeUID : null; + this.dto = dto; + } + + public boolean matches(ThingTypeUID otherThingTypeUID) { + return thingTypeUID.equals(otherThingTypeUID); + } + + public int[] getSignalLevels() { + return groups.contains(GROUP_SIGNAL) + ? (groups.contains(GROUP_BATTERY) ? RADIO_SIGNAL_LEVELS : WIFI_SIGNAL_LEVELS) + : NO_RADIO; + } + + public List getGroups() { + return groups; + } + + public List getExtensions() { + return extensions; + } + + public RefreshPolicy getRefreshPeriod() { + return refreshPeriod; + } + + public @Nullable ThingTypeUID getBridgeThingType() { + return bridgeThingType; + } + + public @Nullable Constructor getHandlerConstructor() throws NoSuchMethodException, SecurityException { + return handlerClass != null + ? handlerClass.getConstructor(Bridge.class, List.class, ApiBridge.class, + NetatmoDescriptionProvider.class) + : null; + } + + public List> getChannelHelpers() { + return channelHelpers; + } + + public @Nullable Class getDto() { + return dto; + } + + public ThingTypeUID getThingTypeUID() { + return thingTypeUID; + } + + public static Boolean isModuleTypeImplemented(String name) { + if (name.isBlank()) { + return false; + } + for (ModuleType item : ModuleType.values()) { + if (name.equals(item.toString())) { + return true; + } + } + return false; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoConstants.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoConstants.java new file mode 100644 index 0000000000000..8396171ae3384 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoConstants.java @@ -0,0 +1,317 @@ +/** + * Copyright (c) 2010-2021 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.core.library.unit.MetricPrefix.*; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; + +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 int maxValue; + public final double precision; + public final int scale; + public final Unit unit; + + public Measure(double minValue, int maxValue, double precision, Unit unit) { + this.minValue = minValue; + this.maxValue = maxValue; + this.precision = precision; + this.unit = unit; + String[] splitter = Double.valueOf(precision).toString().split("\\."); + this.scale = splitter.length > 1 ? splitter[1].length() : 0; + } + } + + public enum MeasureClass { + INTERIOR_TEMPERATURE, + EXTERIOR_TEMPERATURE, + PRESSURE, + CO2, + NOISE, + RAIN_QTTY, + RAIN_INTENSITY, + WIND_SPEED, + WIND_ANGLE, + HUMIDITY; + } + + public static final Map NA_MEASURES = Map.of(MeasureClass.INTERIOR_TEMPERATURE, + new Measure(0, 50, 0.3, SIUnits.CELSIUS), MeasureClass.EXTERIOR_TEMPERATURE, + new Measure(-40, 65, 0.3, SIUnits.CELSIUS), MeasureClass.PRESSURE, + new Measure(260, 1260, 1, HECTO(SIUnits.PASCAL)), MeasureClass.CO2, + new Measure(0, 5000, 50, Units.PARTS_PER_MILLION), MeasureClass.NOISE, + new Measure(35, 120, 1, Units.DECIBEL), MeasureClass.RAIN_QTTY, + new Measure(0.2, 150, 0.1, MILLI(SIUnits.METRE)), MeasureClass.RAIN_INTENSITY, + new Measure(0.2, 150, 0.1, Units.MILLIMETRE_PER_HOUR), MeasureClass.WIND_SPEED, + new Measure(0, 160, 1.8, SIUnits.KILOMETRE_PER_HOUR), MeasureClass.WIND_ANGLE, + new Measure(0, 360, 5, Units.DEGREE_ANGLE), MeasureClass.HUMIDITY, new Measure(0, 100, 3, Units.PERCENT)); + + // Netatmo API urls + public static final String NA_API_URL = "https://api.netatmo.com/"; + public static final String NA_APP_URL = "https://app.netatmo.net/"; + public static final String NA_OAUTH_PATH = "oauth2/token"; + public static final String NA_API_PATH = "api"; + public static final String NA_COMMAND_PATH = "command"; + public static final String NA_PERSON_AWAY_SPATH = "setpersonsaway"; + public static final String NA_PERSON_HOME_SPATH = "setpersonshome"; + public static final String NA_HOMES_SPATH = "homesdata"; + public static final String NA_GETHOME_SPATH = "gethomedata"; + public static final String NA_GETCAMERAPICTURE_SPATH = "getcamerapicture"; + public static final String NA_ADDWEBHOOK_SPATH = "addwebhook"; + public static final String NA_DROPWEBHOOK_SPATH = "dropwebhook"; + public static final String NA_SETROOMTHERMPOINT_SPATH = "setroomthermpoint"; + public static final String NA_SETTHERMMODE_SPATH = "setthermmode"; + public static final String NA_SWITCHSCHEDULE_SPATH = "switchschedule"; + public static final String NA_GETTHERMOSTAT_SPATH = "getthermostatsdata"; + public static final String NA_GETSTATION_SPATH = "getstationsdata"; + public static final String NA_GETMEASURE_SPATH = "getmeasure"; + public static final String NA_HOMESTATUS_SPATH = "homestatus"; + public static final String NA_HOMECOACH_SPATH = "gethomecoachsdata"; + public static final String NA_GETLASTEVENT_SPATH = "getlasteventof"; + + public static final String NA_DEVICEID_PARAM = "device_id"; + public static final String NA_MODULEID_PARAM = "module_id"; + public static final String NA_HOMEID_PARAM = "home_id"; + public static final String NA_ROOMID_PARAM = "room_id"; + + public enum MeasureType { + SUM_RAIN(MeasureClass.RAIN_QTTY), + TEMP(MeasureClass.EXTERIOR_TEMPERATURE), + HUM(MeasureClass.EXTERIOR_TEMPERATURE), + CO2(MeasureClass.CO2), + NOISE(MeasureClass.NOISE), + PRESSURE(MeasureClass.PRESSURE), + WIND(MeasureClass.WIND_SPEED); + + private MeasureClass unit; + + MeasureType(MeasureClass unit) { + this.unit = unit; + } + + public MeasureClass getUnit() { + return unit; + } + } + + public enum MeasureLimit { + MIN, + MAX, + DATE_MIN, + DATE_MAX, + NONE; + } + + public enum MeasureScale { + THIRTY_MINUTES("30min"), + ONE_HOUR("1hour"), + THREE_HOURS("3hours"), + ONE_DAY("1day"), + ONE_WEEK("1week"), + ONE_MONTH("1month"); + + private static Map stringMap = Arrays.stream(values()) + .collect(Collectors.toMap(MeasureScale::getDescriptor, Function.identity())); + + private final String apiDescriptor; + + MeasureScale(String value) { + this.apiDescriptor = value; + } + + public String getDescriptor() { + return apiDescriptor; + } + + public static @Nullable MeasureScale from(String descriptor) { + return stringMap.get(descriptor); + } + } + + // 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("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; + } + + static final Set WEATHER_SCOPES = Set.of(Scope.READ_STATION); + private static final Set SMOKE_SCOPES = Set.of(Scope.READ_SMOKEDETECTOR); + static final Set AIR_QUALITY_SCOPES = Set.of(Scope.READ_HOMECOACH); + static final Set ENERGY_SCOPES = Set.of(Scope.READ_THERMOSTAT, Scope.WRITE_THERMOSTAT); + private static final Set WELCOME_SCOPES = Set.of(Scope.READ_CAMERA, Scope.WRITE_CAMERA, Scope.ACCESS_CAMERA); + private static final Set DOORBELL_SCOPES = Set.of(Scope.READ_DOORBELL, Scope.WRITE_DOORBELL, + Scope.ACCESS_DOORBELL); + private static final Set PRESENCE_SCOPES = Set.of(Scope.READ_PRESENCE, Scope.ACCESS_PRESENCE); + static final Set SECURITY_SCOPES = Stream.of(WELCOME_SCOPES, PRESENCE_SCOPES, SMOKE_SCOPES, DOORBELL_SCOPES) + .flatMap(Set::stream).collect(Collectors.toSet()); + static final Set ALL_SCOPES = Stream.of(WEATHER_SCOPES, ENERGY_SCOPES, SECURITY_SCOPES, AIR_QUALITY_SCOPES) + .flatMap(Set::stream).collect(Collectors.toSet()); + + // 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 final int[] NO_RADIO = new int[0]; // No radio => no levels + + // 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"), + UNKNOWN(""), + @SerializedName("schedule") + SCHEDULE("schedule"), + HOME("home"); + + String apiDescriptor; + + SetpointMode(String descriptor) { + this.apiDescriptor = descriptor; + } + + public String getDescriptor() { + return apiDescriptor; + } + + // TODO Remove unused code found by UCDetector + // public static SetpointMode fromName(String name) { + // return Arrays.stream(values()).filter(value -> value.apiDescriptor.equals(name)).findFirst() + // .orElse(UNKNOWN); + // } + } + + 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(""); + + String zoneId; + + private ThermostatZoneType(String id) { + zoneId = id; + } + + public static ThermostatZoneType fromId(String id) { + return Arrays.stream(values()).filter(value -> value.zoneId.equals(id)).findFirst().orElse(UNKNOWN); + } + } + + // Presence + public enum PresenceLightMode { + @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; + } +} 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 index 0000000000000..799500f5b2c8b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoException.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * An exception that occurred while communicating with an automower or an automower bridge + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NetatmoException extends IOException { + private static final long serialVersionUID = 1513549973502021727L; + private int statusCode = -1; + + public NetatmoException(Exception e) { + super(e); + } + + // TODO Remove unused code found by UCDetector + // public NetatmoException(int statusCode, Exception e) { + // super(e); + // this.statusCode = statusCode; + // } + + // TODO Remove unused code found by UCDetector + // public NetatmoException(int statusCode) { + // this.statusCode = statusCode; + // } + + public NetatmoException(int statusCode, String message) { + super(message); + this.statusCode = statusCode; + } + + public NetatmoException(String message, Exception e) { + super(message, e); + } + + public NetatmoException(String message) { + super(message); + } + + public int getStatusCode() { + return statusCode; + } + + @Override + public @Nullable String getMessage() { + String message = super.getMessage(); + return message == null ? null : "Rest call failed: statusCode=" + statusCode + ", message=" + message; + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": statusCode=" + statusCode + ", message=" + super.getMessage() + + ", cause: " + getCause(); + } +} 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 index 0000000000000..726c253ff6504 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/RestManager.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.*; + +import java.net.URI; +import java.util.Collections; +import java.util.Set; + +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.NetatmoConstants.Scope; + +/** + * 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(NA_API_URL); + private static final UriBuilder OAUTH_URI_BUILDER = API_BASE_BUILDER.clone().path(NA_OAUTH_PATH); + private static final UriBuilder API_URI_BUILDER = API_BASE_BUILDER.clone().path(NA_API_PATH); + private static final UriBuilder APP_URI_BUILDER = UriBuilder.fromUri(NA_APP_URL).path(NA_API_PATH); + // private static final UriBuilder SNAPSHOT_URI_BUILDER = API_URI_BUILDER.clone().path(NA_GETCAMERAPICTURE_SPATH); + + private final Set requiredScopes; + protected final ApiBridge apiHandler; + + public RestManager(ApiBridge apiHandler) { + this(apiHandler, Collections.emptySet()); + } + + public RestManager(ApiBridge apiHandler, Set requiredScopes) { + this.apiHandler = apiHandler; + this.requiredScopes = requiredScopes; + } + + public Set getRequiredScopes() { + return requiredScopes; + } + + public > T get(UriBuilder uriBuilder, Class classOfT) throws NetatmoException { + return apiHandler.executeUri(uriBuilder.build(), HttpMethod.GET, classOfT, null); + } + + public > T post(UriBuilder uriBuilder, Class classOfT, @Nullable String payload) + throws NetatmoException { + T response = apiHandler.executeUri(uriBuilder.build(), HttpMethod.POST, classOfT, payload); + if (response instanceof ApiOkResponse) { + ApiOkResponse okResponse = (ApiOkResponse) response; + if (!okResponse.isSuccess()) { + throw new NetatmoException(String.format("Unsuccessfull command : %s for uri : %s", + response.getStatus(), uriBuilder.build().toString())); + } + } + return response; + } + + protected UriBuilder getApiUriBuilder() { + return API_URI_BUILDER.clone(); + } + + protected UriBuilder getAppUriBuilder() { + return APP_URI_BUILDER.clone(); + } + + // TODO Remove unused code found by UCDetector + // protected UriBuilder getSnapshotUriBuilder() { + // return SNAPSHOT_URI_BUILDER.clone(); + // } + + protected URI getOAuthUri() { + return OAUTH_URI_BUILDER.build(); + } +} 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 index 0000000000000..0e513a8b455a8 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/SecurityApi.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.*; + +import java.net.URI; +import java.util.List; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeEvent; +import org.openhab.binding.netatmo.internal.api.dto.NALastEventsData; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class SecurityApi extends RestManager { + public SecurityApi(ApiBridge apiClient) { // NO_UCD (unused code) + super(apiClient, NetatmoConstants.SECURITY_SCOPES); + } + + // /** + // * + // * Returns information about users homes and cameras. + // * + // * @param homeId Specify if you're looking for the events of a specific Home. (optional) + // * @param size Number of events to retrieve. Default is 30. (optional) + // * @return NAWelcomeHomeDataResponse + // * @throws NetatmoException If fail to call the API, e.g. server error or cannot deserialize the + // * response body + // */ + // private NAObjectMap getWelcomeData(@Nullable String homeId) throws NetatmoException { + // // String req = "gethomedata"; + // // if (homeId != null) { + // // req += "?home_id=" + homeId; + // // } + // // NAHomesDataResponse response = get(req, NAHomesDataResponse.class); + // // return response; + // + // UriBuilder apiUriBuilder = getApiUriBuilder(); + // apiUriBuilder = apiUriBuilder.path(NA_GETHOMEDATA_SPATH); + // + // if (homeId != null) { + // apiUriBuilder.queryParam(NA_HOMEID_PARAM, homeId); + // } + // NAHomesDataResponse response = get(apiUriBuilder.build(), NAHomesDataResponse.class); + // return response.getBody(); + // } + + // public NAHome getWelcomeHomeData(String homeId) throws NetatmoException { + // // NAHomesDataResponse response = getWelcomeData(homeId); + // // return response.getBody().getHomes().get(0); + // + // NAObjectMap homeList = getHomeData(null); + // NAHome home = homeList.get(homeId); + // if (home != null) { + // return home; + // } + // throw new NetatmoException(String.format("Home %s was not found", homeId)); + // } + + // public NAHomeData getWelcomeDataBody() throws NetatmoException { + // return getWelcomeData(null).getBody(); + // } + + /** + * + * Dissociates a webhook from a user. + * + * @return boolean Success + * @throws NetatmoException If fail to call the API, e.g. server error or cannot deserialize the + * response body + */ + public boolean dropWebhook() throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_DROPWEBHOOK_SPATH); + post(uriBuilder, ApiOkResponse.class, null); + return true; + } + + /** + * + * Links a callback url to a user. + * + * @param uri Your webhook callback url (required) + * @return boolean Success + * @throws NetatmoException If fail to call the API, e.g. server error or cannot deserialize the + * response body + */ + public boolean addwebhook(URI uri) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_ADDWEBHOOK_SPATH); + uriBuilder.queryParam("url", uri.toString()); + post(uriBuilder, ApiOkResponse.class, null); + return true; + } + + private class NALastEventsDataResponse extends ApiResponse { + } + + public List getLastEventOf(String homeId, String personId) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_GETLASTEVENT_SPATH); + uriBuilder.queryParam(NA_HOMEID_PARAM, homeId); + uriBuilder.queryParam("person_id", personId); + NALastEventsDataResponse response = get(uriBuilder, NALastEventsDataResponse.class); + return response.getBody().getEvents(); + } +} 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 index 0000000000000..0b73575b636ba --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.*; + +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.NetatmoConstants.MeasureLimit; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureScale; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureType; +import org.openhab.binding.netatmo.internal.api.dto.NADeviceDataBody; +import org.openhab.binding.netatmo.internal.api.dto.NAMain; +import org.openhab.binding.netatmo.internal.api.dto.NAMeasureBodyElem; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class WeatherApi extends RestManager { + public class NAStationDataResponse extends ApiResponse> { + } + + private class NAMeasuresResponse extends ApiResponse> { + } + + public WeatherApi(ApiBridge apiClient) { + super(apiClient, NetatmoConstants.WEATHER_SCOPES); + } + + /** + * + * The method getstationsdata 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 NAStationDataResponse + * @throws NetatmoException If fail to call the API, e.g. server error or cannot deserialize the + * response body + */ + public NAStationDataResponse getStationsData(@Nullable String deviceId, boolean getFavorites) + throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_GETSTATION_SPATH); + if (deviceId != null) { + uriBuilder.queryParam(NA_DEVICEID_PARAM, deviceId); + } + uriBuilder.queryParam("get_favorites", getFavorites); + NAStationDataResponse response = get(uriBuilder, NAStationDataResponse.class); + return response; + } + + public NAMain getStationData(String deviceId) throws NetatmoException { + NADeviceDataBody answer = getStationsData(deviceId, true).getBody(); + NAMain station = answer.getDevice(deviceId); + if (station != null) { + return station; + } + throw new NetatmoException(String.format("Unexpected answer cherching device '%s' : not found.", deviceId)); + } + + public double getMeasurements(String deviceId, @Nullable String moduleId, MeasureScale scale, MeasureType type, + MeasureLimit limit) throws NetatmoException { + List result = getmeasure(deviceId, moduleId, scale, + (limit.toString() + "_" + type.toString()).toLowerCase(), 0, 0); + return result.size() > 0 ? result.get(0).getSingleValue() : Double.NaN; + } + + public double getMeasurements(String deviceId, @Nullable String moduleId, MeasureScale scale, MeasureType type) + throws NetatmoException { + List result = getmeasure(deviceId, moduleId, scale, type.toString().toLowerCase(), 0, 0); + return result.size() > 0 ? result.get(0).getSingleValue() : Double.NaN; + } + + private List getmeasure(String deviceId, @Nullable String moduleId, MeasureScale scale, + String measureType, long dateBegin, long dateEnd) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder().path(NA_GETMEASURE_SPATH).queryParam(NA_DEVICEID_PARAM, deviceId) + .queryParam("scale", scale.getDescriptor()).queryParam("real_time", true) + .queryParam("date_end", (dateEnd > 0 && dateEnd > dateBegin) ? dateEnd : "last") + .queryParam("optimize", true) // NAMeasuresResponse is not designed for optimize=false + .queryParam("type", measureType); + + if (moduleId != null) { + uriBuilder.queryParam(NA_MODULEID_PARAM, moduleId); + } + if (dateBegin > 0) { + uriBuilder.queryParam("date_begin", dateBegin); + } + NAMeasuresResponse response = get(uriBuilder, NAMeasuresResponse.class); + return response.getBody(); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAAccessTokenResponse.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAAccessTokenResponse.java new file mode 100644 index 0000000000000..829fcea8e0471 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAAccessTokenResponse.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2010-2021 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.io.Serializable; +import java.util.List; +import java.util.Objects; + +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.Scope; + +/** + * This is the Access Token Response, a simple value-object that holds the result of the + * from an Access Token Request, as provided by Netatmo API : + * + * @author Gaël L'hopital - Adapted from OH core implementation for Netatmo API + */ +public final class NAAccessTokenResponse implements Serializable, Cloneable { + + /** + * For Serializable + */ + private static final long serialVersionUID = 5512401378281693003L; + + /** + * The access token issued by the authorization server. It is used + * by the client to gain access to a resource. + * + *

+ * This token must be confidential in transit and storage. + * + * @see rfc6749 section-1.4 + * @see rfc6749 section-10.3 + */ + private String accessToken; + + /** + * Number of seconds that this OAuthToken is valid for since the time it was created. + * + * @see rfc6749 section-4.2.2 + */ + 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. + * + *

+ * This token must be confidential in transit and storage. + * + * @see rfc6749 section-1.5 + * @see rfc6749 section-10.4 + */ + private String refreshToken; + + private List scope; + + public String getAccessToken() { + return accessToken; + } + + public long getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(long expiresIn) { + this.expiresIn = expiresIn; + } + + public String getRefreshToken() { + return refreshToken; + } + + public List getScope() { + return scope; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException("not possible", e); + } + } + + @Override + public int hashCode() { + return Objects.hash(accessToken, expiresIn, refreshToken, scope); + } + + @Override + public boolean equals(Object thatAuthTokenObj) { + if (this == thatAuthTokenObj) { + return true; + } + + // Exact match since class is final + if (thatAuthTokenObj == null || !this.getClass().equals(thatAuthTokenObj.getClass())) { + return false; + } + + NAAccessTokenResponse that = (NAAccessTokenResponse) thatAuthTokenObj; + + return Objects.equals(this.accessToken, that.accessToken) && Objects.equals(this.expiresIn, that.expiresIn) + && Objects.equals(this.refreshToken, that.refreshToken) && Objects.equals(this.scope, that.scope); + } + + @Override + public String toString() { + return "NAAccessTokenResponse [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/NADashboard.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADashboard.java new file mode 100644 index 0000000000000..3876d90257445 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADashboard.java @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.TrendDescription; + +import com.google.gson.annotations.SerializedName; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class NADashboard { + private long 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 long 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 double getHealthIdx() { + return healthIdx; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADevice.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADevice.java new file mode 100644 index 0000000000000..f1d10dbcbcf0f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADevice.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2021 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.ModuleType; +import org.openhab.binding.netatmo.internal.deserialization.NADynamicObjectMap; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class NADevice extends NAThing { + private NADynamicObjectMap modules = new NADynamicObjectMap(); + private boolean co2Calibrating; + private long dateSetup; + private long lastUpgrade; + private @Nullable NAPlace place; + + public NADynamicObjectMap getModules() { + return modules; + } + + // TODO : faut-il garder ce setter ? + public void setModules(NADynamicObjectMap modules) { + this.modules = modules; + } + + public @Nullable NAThing getModule(String key) { + return modules.get(key); + } + + public long getDateSetup() { + return dateSetup; + } + + public long getLastUpgrade() { + return lastUpgrade; + } + + public @Nullable NAPlace getPlace() { + return place; + } + + public void setPlace(@Nullable NAPlace place) { + this.place = place; + } + + public boolean isCo2Calibrating() { + return co2Calibrating; + } + + public boolean containsModuleType(ModuleType searchedType) { + return modules.values().stream().anyMatch(module -> module.getType() == searchedType); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADeviceDataBody.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADeviceDataBody.java new file mode 100644 index 0000000000000..311b6ee0e0b64 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NADeviceDataBody.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2021 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.deserialization.NAObjectMap; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class NADeviceDataBody { + private @NonNullByDefault({}) NAObjectMap devices; + // private @NonNullByDefault({}) NAUser user; + + public NAObjectMap getDevices() { + return devices; + } + + public @Nullable T getDevice(String id) { + return devices.get(id); + } + + // public NAUser getUser() { + // return user; + // } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAEvent.java new file mode 100644 index 0000000000000..3ca8eeac47bd3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAEvent.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2021 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.EventSubType; +import org.openhab.binding.netatmo.internal.api.EventType; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public abstract class NAEvent extends NAObject { + protected EventType type = EventType.UNKNOWN; + private @NonNullByDefault({}) String cameraId; + private @NonNullByDefault({}) String message; + protected int subType = -1; + + public EventType getEventType() { + return type; + } + + public String getCameraId() { + return cameraId; + } + + public String getMessage() { + return message.replace("", "").replace("", ""); + } + + public abstract ZonedDateTime getTime(); + + public abstract @Nullable String getPersonId(); + + public abstract @Nullable NASnapshot getSnapshot(); + + public Optional getSubTypeDescription() { + return Stream.of(EventSubType.values()).filter(v -> v.getType() == getEventType() && v.getSubType() == 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/NAHome.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHome.java new file mode 100644 index 0000000000000..e401b9707e95b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHome.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2021 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.core.library.types.DecimalType; +import org.openhab.core.library.types.PointType; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAHome extends NADevice { + private double[] location = {}; + private double altitude; + private List rooms = List.of(); + + public @Nullable PointType getLocation() { + return location.length != 2 ? null + : new PointType(new DecimalType(location[1]), new DecimalType(location[0]), new DecimalType(altitude)); + } + + public List getRooms() { + return rooms; + } + + // TODO : dégager setters ? + public void setRooms(List rooms) { + this.rooms = rooms; + } + + public @Nullable NARoom getRoom(String id) { + return rooms.stream().filter(r -> r.getId().equals(id)).findFirst().orElse(null); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeData.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeData.java new file mode 100644 index 0000000000000..346288e07dc63 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeData.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAHomeData { + private List homes = List.of(); + + public List getHomes() { + return homes; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeEnergy.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeEnergy.java new file mode 100644 index 0000000000000..d912b587c21e8 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeEnergy.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2021 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.SetpointMode; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAHomeEnergy extends NAHome { + private List schedules = List.of(); + private SetpointMode thermMode = SetpointMode.UNKNOWN; + private @Nullable ZonedDateTime thermModeEndtime; + private int thermSetpointDefaultDuration; + + public List getThermSchedules() { + return schedules; + } + + public @Nullable NAThermProgram getActiveProgram() { + return schedules.stream().filter(NAThermProgram::isSelected).findFirst().orElse(null); + } + + public @Nullable ZonedDateTime getThermModeEndTime() { + return thermModeEndtime; + } + + public int getThermSetpointDefaultDuration() { + return thermSetpointDefaultDuration; + } + + public SetpointMode getThermMode() { + return thermMode; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeEvent.java new file mode 100644 index 0000000000000..390c1bef10b66 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeEvent.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010-2021 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.EventSubType; +import org.openhab.binding.netatmo.internal.api.EventType; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.EventCategory; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.VideoStatus; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAHomeEvent extends NAEvent { + private @NonNullByDefault({}) ZonedDateTime time; + private @Nullable String personId; + private EventCategory category = EventCategory.UNKNOWN; + private @Nullable NASnapshot 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 getSubTypeDescription() { + // Blend extra informations provided by this kind of event in subcategories... + if (isArrival && type == EventType.PERSON) { + this.subType = EventSubType.ARRIVAL.getSubType(); + } else { + switch (category) { + case ANIMAL: + this.subType = EventSubType.ANIMAL.getSubType(); + break; + case HUMAN: + this.subType = EventSubType.HUMAN.getSubType(); + break; + case VEHICLE: + this.subType = EventSubType.VEHICLE.getSubType(); + break; + default: + break; + } + } + // ... and let ancestor do his work + return super.getSubTypeDescription(); + } + + @Override + public @Nullable NASnapshot getSnapshot() { + return this.snapshot; + } + + public void setTime(ZonedDateTime eventTime) { + this.time = eventTime; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeSecurity.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeSecurity.java new file mode 100644 index 0000000000000..95236b1675437 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeSecurity.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2021 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.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAHomeSecurity extends NAHome { + private NAObjectMap persons = new NAObjectMap(); + private NAObjectMap cameras = new NAObjectMap(); + private List events = List.of(); + + public NAObjectMap getPersons() { + return persons; + } + + public List getKnownPersons() { + return persons.values().stream().filter(person -> person.getName() != null).collect(Collectors.toList()); + } + + public List getEvents() { + return events; + } + + public NAObjectMap getCameras() { + return cameras; + } + + // TODO Remove unused code found by UCDetector + // public Optional getPerson(String id) { + // return Optional.ofNullable(persons.get(id)); + // } + + public void setEvents(List events) { + this.events = events; + } +} 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 index 0000000000000..36b7b1da09fd1 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +public class NAHomeStatus { + private NAHome home; + + public NAHome getHome() { + return home; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeWeather.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeWeather.java new file mode 100644 index 0000000000000..ba81c29e252f6 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeWeather.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAHomeWeather extends NAHome { +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NALastEventsData.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NALastEventsData.java new file mode 100644 index 0000000000000..60381c14fad80 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NALastEventsData.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NALastEventsData { + private List eventsList = List.of(); + + public List getEvents() { + return eventsList; + } +} 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 index 0000000000000..2956d24244108 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMain.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAMain extends NADevice { + private boolean readOnly; + private boolean favorite; + + /** + * true when the device is a user favorite and not owned by them + * + * @return favorite + **/ + public boolean isFavorite() { + return favorite; + } + + /** + * 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; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMeasureBodyElem.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMeasureBodyElem.java new file mode 100644 index 0000000000000..b02c3ec5df225 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMeasureBodyElem.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAMeasureBodyElem { + private long begTime; + private long stepTime; + private List> value = List.of(); + + public long getBegTime() { + return begTime; + } + + public long getStepTime() { + return stepTime; + } + + public List> getValue() { + return value; + } + + public Double getSingleValue() { + if (value.size() > 0) { + List first = value.get(0); + if (first.size() > 0) { + return first.get(0); + } + } + return Double.NaN; + } +} 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 index 0000000000000..54cb947e4294e --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAModule.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAModule extends NAThing { + // TODO : @Mdillman : can batteryState be changed to an enum ? + private @Nullable String batteryState; + private int batteryPercent; + + public int getBatteryPercent() { + return batteryPercent; + } + + public @Nullable String getBatteryState() { + return this.batteryState; + } +} 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 index 0000000000000..e13ef1eec6ef6 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAObject.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2021 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 @NonNullByDefault({}) String id; + + @SerializedName(value = "name", alternate = { "module_name", "station_name", "pseudo" }) + private @Nullable String name; + + public String getId() { + return id; + } + + public @Nullable String getName() { + return name; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPerson.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPerson.java new file mode 100644 index 0000000000000..8bb39c021ab27 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPerson.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2021 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.ModuleType; + +/** + * NAHomePerson + * This class merges answers provided in event and in webhook to provide the + * same interface to the binding + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAPerson extends NAModule { + // Provided by events + private boolean outOfSight; + private @Nullable NASnapshot face; + + // Provided by webhooks + private @Nullable String faceId; + private @Nullable String faceKey; + private boolean isKnown; + + @Override + public ModuleType getType() { + return ModuleType.NAPerson; + } + + public boolean isOutOfSight() { + return outOfSight; + } + + public @Nullable NASnapshot getFace() { + String fId = faceId; + String key = faceKey; + if (face == null && fId != null && key != null) { + face = new NASnapshot(fId, key); + } + return face; + } + + public boolean isKnown() { + return isKnown; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPing.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPing.java new file mode 100644 index 0000000000000..8e6d2cb00da49 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPing.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2021 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.ApiResponse; + +/** + * The {@link NAPing} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class NAPing extends ApiResponse { + private @NonNullByDefault({}) String localUrl; + private @NonNullByDefault({}) String productName; + + @Override + public String getStatus() { + return localUrl; + } + + @Override + public String getBody() { + return productName; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPlace.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPlace.java new file mode 100644 index 0000000000000..ad801713ae8fb --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPlace.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2021 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.core.library.types.DecimalType; +import org.openhab.core.library.types.PointType; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAPlace { + private @NonNullByDefault({}) String city; + private @NonNullByDefault({}) String country; + private @NonNullByDefault({}) String timezone; + private @Nullable String street; + private double altitude; + private double[] location = {}; + + public String getCity() { + return city; + } + + public @Nullable String getStreet() { + return street; + } + + public String getCountry() { + return country; + } + + public String getTimezone() { + return timezone; + } + + public @Nullable PointType getLocation() { + if (location.length == 2) { + return new PointType(new DecimalType(location[1]), new DecimalType(location[0]), new DecimalType(altitude)); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPlug.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPlug.java new file mode 100644 index 0000000000000..e868a5a76cc63 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAPlug.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2021 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.time.temporal.TemporalAdjusters; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * + * @author Gaël L'hopital - Initial contribution + */ + +@NonNullByDefault +public class NAPlug extends NAModule { + private @Nullable OpenClosedType plugConnectedBoiler; + private Map lastBilan = Map.of(); + + public State getPlugConnectedBoiler() { + OpenClosedType connected = plugConnectedBoiler; + return connected != null ? connected : UnDefType.NULL; + } + + public @Nullable ZonedDateTime getLastBilan() { + Integer year = lastBilan.get("y"); + Integer month = lastBilan.get("m"); + if (year != null && month != null) { + return ZonedDateTime.of(year, month, 1, 0, 0, 0, 0, ZonedDateTime.now().getZone()) + .with(TemporalAdjusters.lastDayOfMonth()); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NARoom.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NARoom.java new file mode 100644 index 0000000000000..08ac1240ee824 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NARoom.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2010-2021 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.ModuleType; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.SetpointMode; + +/** + * + * @author Bernhard Kreuz - Initial contribution + * + */ +@NonNullByDefault +public class NARoom extends NAThing { + private boolean anticipating; + private int heatingPowerRequest; + private boolean openWindow; + private double thermMeasuredTemperature; + private SetpointMode thermSetpointMode = SetpointMode.UNKNOWN; + private double thermSetpointTemperature; + private @Nullable ZonedDateTime thermSetpointStartTime; + private @Nullable ZonedDateTime thermSetpointEndTime; + + /** + * @return the anticipating + */ + public boolean isAnticipating() { + return anticipating; + } + + /** + * @return the heatingPowerRequest + */ + public int getHeatingPowerRequest() { + return heatingPowerRequest; + } + + /** + * @return the openWindow + */ + public boolean isOpenWindow() { + return openWindow; + } + + /** + * @return the thermMeasuredTemperature + */ + public Double getThermMeasuredTemperature() { + return thermMeasuredTemperature; + } + + /** + * @return the thermSetpointMode + */ + public SetpointMode getThermSetpointMode() { + return thermSetpointMode; + } + + /** + * @return the thermSetpointTemperature + */ + public double getThermSetpointTemperature() { + return thermSetpointTemperature; + } + + public @Nullable ZonedDateTime getThermSetpointStartTime() { + return thermSetpointStartTime; + } + + public @Nullable ZonedDateTime getThermSetpointEndTime() { + return thermSetpointEndTime; + } + + @Override + public ModuleType getType() { + // In json api answer type for NARoom is used with words like kitchen, living... + // Maybe NARoom should not inherit from NAThing + return ModuleType.NARoom; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NASetpoint.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NASetpoint.java new file mode 100644 index 0000000000000..fa343b8c65674 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NASetpoint.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.SetpointMode; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NASetpoint { + private double thermSetpointTemperature; + private long setpointEndtime; + private SetpointMode setpointMode = SetpointMode.UNKNOWN; + + public double getSetpointTemperature() { + return thermSetpointTemperature; + } + + public long getSetpointEndtime() { + return setpointEndtime; + } + + public SetpointMode getMode() { + return setpointMode; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NASnapshot.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NASnapshot.java new file mode 100644 index 0000000000000..cfe8083fb1ab4 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NASnapshot.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2010-2021 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.openhab.binding.netatmo.internal.api.NetatmoConstants.*; + +import java.net.MalformedURLException; +import java.net.URI; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NASnapshot extends NAObject { + private static UriBuilder URI_BUILDER = UriBuilder.fromUri(NA_API_URL).path(NA_API_PATH) + .path(NA_GETCAMERAPICTURE_SPATH); + private final Logger logger = LoggerFactory.getLogger(NASnapshot.class); + private @Nullable String key; + + public NASnapshot(String id, String key) { + this.id = id; + this.key = key; + } + + public @Nullable String getUrl() { + if (key != null) { + URI uri = URI_BUILDER.clone().queryParam("image_id", id).queryParam("key", key).build(); + try { + return uri.toURL().toString(); + } catch (MalformedURLException e) { + logger.warn("Malformed URI in snapshot : {}", uri); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermMeasure.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermMeasure.java new file mode 100644 index 0000000000000..520cf1bb41bdf --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermMeasure.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAThermMeasure { + private @Nullable ZonedDateTime time; + private double temperature; + private double setpointTemp; + + public @Nullable ZonedDateTime getTime() { + return time; + } + + public double getTemperature() { + return temperature; + } + + public double getSetpointTemp() { + return setpointTemp; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermProgram.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermProgram.java new file mode 100644 index 0000000000000..a9d5bc608d9e9 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermProgram.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2021 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.api.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.ThermostatZoneType; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAThermProgram extends NAObject { + private List zones = List.of(); + private List timetable = List.of(); + private boolean selected; + + public List getTimetable() { + return timetable; + } + + public boolean isSelected() { + return selected; + } + + public double getZoneTemperature(SetpointMode currentMode) { + try { + ThermostatZoneType equivalentZone = ThermostatZoneType.valueOf(currentMode.toString()); + return getZoneTemperature(equivalentZone); + } catch (IllegalArgumentException ignore) { // not all thermostat modes have an equivalent zone + return Double.NaN; + } + } + + public double getZoneTemperature(ThermostatZoneType zone) { + return zones.stream().filter(z -> z.getType() == zone).findFirst().map(NAZone::getTemp).orElse(Double.NaN); + } + + public @Nullable NAZone getZone(String id) { + return zones.stream().filter(r -> r.getId().equals(id)).findFirst().orElse(null); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermostat.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermostat.java new file mode 100644 index 0000000000000..a905bfa45a79b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThermostat.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2010-2021 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.api.NetatmoConstants.SetpointMode; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAThermostat extends NAModule { + private @Nullable NAThermMeasure measured; + private @Nullable NASetpoint setpoint; + private int thermOrientation; + private int thermRelayCmd; + private boolean anticipating; + + private List thermProgramList = List.of(); + + public @Nullable NAThermMeasure getMeasured() { + return measured; + } + + public int getThermOrientation() { + return thermOrientation; + } + + public List getThermProgramList() { + return thermProgramList; + } + + public @Nullable NAThermProgram getActiveProgram() { + return thermProgramList.stream().filter(NAThermProgram::isSelected).findFirst().orElse(null); + } + + public boolean getThermRelayCmd() { + return thermRelayCmd != 0; + } + + public double getSetpointTemp() { + NASetpoint localSetpoint = setpoint; + return localSetpoint != null ? localSetpoint.getSetpointTemperature() : Double.NaN; + } + + public long getSetpointEndtime() { + NASetpoint localSetpoint = setpoint; + return localSetpoint != null ? localSetpoint.getSetpointEndtime() : 0; + } + + public SetpointMode getSetpointMode() { + NASetpoint localSetpoint = setpoint; + return localSetpoint != null ? localSetpoint.getMode() : SetpointMode.UNKNOWN; + } + + public boolean isAnticipating() { + return anticipating; + } +} 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 index 0000000000000..a41dc4b77bbaf --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThing.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2010-2021 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.ModuleType; + +import com.google.gson.annotations.SerializedName; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAThing extends NAObject { + @SerializedName(value = "rf_status", alternate = { "wifi_status", "rf_strength" }) + private int radioStatus; + @SerializedName(value = "last_seen", alternate = { "last_therm_seen", "last_status_store", "last_plug_seen", + "last_message", "last_activity" }) + private @Nullable ZonedDateTime lastSeen; + @SerializedName(value = "firmware", alternate = { "firmware_revision" }) + private int firmware = -1; + private @Nullable ZonedDateTime setupDate; + private @NonNullByDefault({}) ModuleType type; + private @Nullable String roomId; + private @Nullable Boolean reachable; + private @Nullable NADashboard dashboardData; + private @Nullable String bridge; + + 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 @Nullable NADashboard getDashboardData() { + return dashboardData; + } + + public ModuleType getType() { + return type; + } + + public void setType(ModuleType type) { + this.type = type; + } + + public int getFirmware() { + return firmware; + } + + public int getRadioStatus() { + return radioStatus; + } + + public Optional getLastSeen() { + return Optional.ofNullable(lastSeen); + } + + public @Nullable ZonedDateTime getSetupDate() { + return setupDate; + } + + 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/NATimeTableItem.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NATimeTableItem.java new file mode 100644 index 0000000000000..e4fc302d671c3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NATimeTableItem.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.ThermostatZoneType; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NATimeTableItem extends NAObject { + private int mOffset; + private int zoneId; + + public int getMOffset() { + return mOffset; + } + + public int getZoneId() { + return zoneId; + } + + public ThermostatZoneType getZoneType() { + return ThermostatZoneType.fromId(getId()); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAWebhookEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAWebhookEvent.java new file mode 100644 index 0000000000000..6b6a4417e2083 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAWebhookEvent.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2010-2021 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.EventType; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.binding.netatmo.internal.deserialization.NAPushType; + +/** + * The {@link NAWebhookEvent} 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 NAWebhookEvent extends NAEvent { + private @NonNullByDefault({}) NAPushType pushType; + private @NonNullByDefault({}) String homeId; + private @Nullable String snapshotId; + private @Nullable String snapshotKey; + private NAObjectMap 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 getPersons() { + return persons; + } + + @Override + public EventType getEventType() { + return pushType.getEvent(); + } + + @Override + public ZonedDateTime getTime() { + return time; + } + + @Override + public @Nullable String getPersonId() { + if (persons.size() > 0) { + return persons.keySet().iterator().next(); + } + return null; + } + + @Override + public @Nullable NASnapshot getSnapshot() { + String sId = snapshotId; + String key = snapshotKey; + if (sId != null && key != null) { + return new NASnapshot(sId, key); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAWelcome.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAWelcome.java new file mode 100644 index 0000000000000..739c583a5683a --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAWelcome.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.PresenceLightMode; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAWelcome extends NAModule { + private @Nullable OnOffType status; + private @Nullable String vpnUrl; + private boolean isLocal; + private @Nullable OnOffType sdStatus; + private @Nullable OnOffType alimStatus; + private PresenceLightMode lightModeStatus = PresenceLightMode.UNKNOWN; + + /** + * If camera is monitoring (on/off) + * + * @return status + **/ + public State getStatus() { + OnOffType localStatus = status; + return localStatus != null ? localStatus : UnDefType.NULL; + } + + /** + * Only for scope access_camera. Address of the camera + * + * @return vpnUrl + **/ + public @Nullable String getVpnUrl() { + return vpnUrl; + } + + /** + * Only for scope access_camera. If Camera and application requesting the information are on the same IP + * (true/false) + * + * @return isLocal + **/ + public boolean isLocal() { + return isLocal; + } + + /** + * If SD card status is ok (on/off) + * + * @return sdStatus + **/ + public State getSdStatus() { + OnOffType sd = sdStatus; + return sd != null ? sd : UnDefType.NULL; + } + + /** + * If power supply is ok (on/off) + * + * @return alimStatus + **/ + public State getAlimStatus() { + OnOffType alim = alimStatus; + return alim != null ? alim : UnDefType.NULL; + } + + /** + * State of (flood-)light + * + * @return lightModeStatus + **/ + public PresenceLightMode getLightModeStatus() { + return lightModeStatus; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAZone.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAZone.java new file mode 100644 index 0000000000000..2159dd298fd54 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAZone.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2021 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.NetatmoConstants.ThermostatZoneType; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAZone 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/CameraHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraHandler.java deleted file mode 100644 index 9e91c97f61256..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraHandler.java +++ /dev/null @@ -1,246 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - - 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; - - 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 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 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 getLiveStreamURL() { - Optional 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 getVpnUrl() { - return getModule().map(NAWelcomeCamera::getVpnUrl); - } - - public Optional getStreamURL(String videoId) { - Optional 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 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 getLocalCameraURL() { - Optional vpnURLOptional = getVpnUrl(); - Optional 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 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 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 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 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/AbstractChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/AbstractChannelHelper.java new file mode 100644 index 0000000000000..f7f3ba3cb9724 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/AbstractChannelHelper.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2010-2021 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 java.time.ZoneId; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NADashboard; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link AbstractChannelHelper} handle specific common behaviour + * of all channel helpers + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public abstract class AbstractChannelHelper { + // TODO : finish removal of ZoneId in channelhelpers + protected final ZoneId zoneId; + protected final Thing thing; + private @Nullable NAThing naThing; + private final Set providedGroup; + + public AbstractChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + this(thing, timeZoneProvider, Set.of()); + } + + public AbstractChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider, Set providedGroup) { + this.zoneId = timeZoneProvider.getTimeZone(); + this.thing = thing; + this.providedGroup = providedGroup; + } + + public void setNewData(NAThing naThing) { + this.naThing = naThing; + } + + public @Nullable State getNAThingProperty(ChannelUID channelUID) { + State result = null; + NAThing module = this.naThing; + if (module != null) { + String channelId = channelUID.getIdWithoutGroup(); + String groupId = channelUID.getGroupId(); + if (providedGroup.isEmpty() || (groupId != null && providedGroup.contains(groupId))) { + result = internalGetProperty(module, channelId); + if (result == null) { + NADashboard dashboard = module.getDashboardData(); + if (dashboard != null) { + result = internalGetDashboard(dashboard, channelId); + } + } + } + } + return result; + } + + protected @Nullable State internalGetDashboard(NADashboard dashboard, String channelId) { + return null; + } + + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + return null; + } +} 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 index db42a5e4a6a5e..462ac17c38b7e 100644 --- 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 @@ -13,21 +13,19 @@ package org.openhab.binding.netatmo.internal.channelhelper; import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -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.ChannelTypeUtils; +import org.openhab.binding.netatmo.internal.api.dto.NAModule; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; +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; /** * The {@link BatteryHelper} handle specific behavior @@ -37,44 +35,26 @@ * */ @NonNullByDefault -public class BatteryHelper { - private final Logger logger = LoggerFactory.getLogger(BatteryHelper.class); - private int batteryLow; +public class BatteryHelper extends AbstractChannelHelper { - private @Nullable Object module; - - public BatteryHelper(String batteryLevels) { - List thresholds = Arrays.asList(batteryLevels.split(",")); - batteryLow = Integer.parseInt(thresholds.get(1)); - } - - public void setModule(Object module) { - this.module = module; + public BatteryHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_BATTERY, GROUP_ENERGY_BATTERY)); } - public Optional 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); + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + if (naThing instanceof NAModule) { + NAModule module = (NAModule) naThing; + int percent = module.getBatteryPercent(); + if (CHANNEL_VALUE.equals(channelId)) { + return new DecimalType(percent); + } else if (CHANNEL_LOW_BATTERY.equals(channelId)) { + return OnOffType.from(percent < 20); + } else if (CHANNEL_BATTERY_STATUS.equals(channelId)) { + String status = module.getBatteryState(); + return toStringType(status); } } - return Optional.empty(); + return null; } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/CameraChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/CameraChannelHelper.java new file mode 100644 index 0000000000000..c01df87c6d8be --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/CameraChannelHelper.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.api.dto.NAWelcome; +import org.openhab.core.i18n.TimeZoneProvider; +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; + +/** + * The {@link CameraChannelHelper} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class CameraChannelHelper extends AbstractChannelHelper { + private static final String LIVE_PICTURE = "/live/snapshot_720.jpg"; + + public CameraChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_WELCOME)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + NAWelcome camera = (NAWelcome) naThing; + switch (channelId) { + case CHANNEL_CAMERA_IS_MONITORING: + return camera.getStatus(); + case CHANNEL_CAMERA_SDSTATUS: + return camera.getSdStatus(); + case CHANNEL_CAMERA_ALIMSTATUS: + return camera.getAlimStatus(); + case CHANNEL_CAMERA_LIVEPICTURE_URL: + return toStringType(getLivePictureURL(camera)); + case CHANNEL_CAMERA_LIVEPICTURE: + return toRawType(getLivePictureURL(camera)); + case CHANNEL_CAMERA_LIVESTREAM_URL: + return getLiveStreamURL(camera); + } + return null; + } + + /** + * Get the url for the live snapshot + * + * @param camera + * + * @return Url of the live snapshot + */ + private @Nullable String getLivePictureURL(NAWelcome camera) { + String result = camera.getVpnUrl(); + if (result != null) { + return result + LIVE_PICTURE; + } + return null; + } + + /** + * Get the url for the live stream depending wether local or not + * + * @return Url of the live stream + */ + private State getLiveStreamURL(NAWelcome camera) { + String result = camera.getVpnUrl(); + if (result != null) { + StringBuilder resultStringBuilder = new StringBuilder(result); + resultStringBuilder.append("/live/index"); + if (camera.isLocal()) { + resultStringBuilder.append("_local"); + } + resultStringBuilder.append(".m3u8"); + return new StringType(resultStringBuilder.toString()); + } + return UnDefType.NULL; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Co2ChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Co2ChannelHelper.java new file mode 100644 index 0000000000000..bb17ff1bec808 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Co2ChannelHelper.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NADashboard; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link Co2ChannelHelper} handle specific channels + * of modules handler ppm measurement + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class Co2ChannelHelper extends AbstractChannelHelper { + + public Co2ChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_CO2)); + } + + @Override + protected @Nullable State internalGetDashboard(NADashboard dashboard, String channelId) { + return CHANNEL_VALUE.equals(channelId) ? toQuantityType(dashboard.getCo2(), MeasureClass.CO2) : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/DeviceChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/DeviceChannelHelper.java new file mode 100644 index 0000000000000..70a37d9c00d6c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/DeviceChannelHelper.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2021 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.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link DeviceChannelHelper} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class DeviceChannelHelper extends AbstractChannelHelper { + + public DeviceChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_DEVICE)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + return CHANNEL_LAST_SEEN.equals(channelId) ? ChannelTypeUtils.toDateTimeType(naThing.getLastSeen()) : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeCoachChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeCoachChannelHelper.java new file mode 100644 index 0000000000000..957edf24ea229 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeCoachChannelHelper.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2021 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.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NADashboard; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link HomeCoachChannelHelper} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HomeCoachChannelHelper extends AbstractChannelHelper { + + public HomeCoachChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_HEALTH)); + } + + @Override + protected @Nullable State internalGetDashboard(NADashboard dashboard, String channelId) { + return CHANNEL_VALUE.equals(channelId) ? new DecimalType(dashboard.getHealthIdx()) : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeEnergyChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeEnergyChannelHelper.java new file mode 100644 index 0000000000000..771a495efe697 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeEnergyChannelHelper.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; +import static org.openhab.binding.netatmo.internal.utils.NetatmoCalendarUtils.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeEnergy; +import org.openhab.binding.netatmo.internal.api.dto.NAThermProgram; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.api.dto.NATimeTableItem; +import org.openhab.binding.netatmo.internal.api.dto.NAZone; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link HomeEnergyChannelHelper} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HomeEnergyChannelHelper extends AbstractChannelHelper { + + public HomeEnergyChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_HOME_ENERGY)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + NAHomeEnergy localThing = (NAHomeEnergy) naThing; + NAThermProgram currentProgram = localThing.getActiveProgram(); + SetpointMode thermMode = localThing.getThermMode(); + switch (channelId) { + case CHANNEL_SETPOINT_DURATION: + return toQuantityType(localThing.getThermSetpointDefaultDuration(), Units.MINUTE); + case CHANNEL_PLANNING: + return (currentProgram != null ? toStringType(currentProgram.getName()) : null); + case CHANNEL_SETPOINT_MODE: + switch (thermMode) { + case PROGRAM: + case HOME: + case SCHEDULE: + NATimeTableItem currentProgramMode = getCurrentProgramMode(localThing.getActiveProgram()); + if (currentProgram != null && currentProgramMode != null) { + NAZone zone = currentProgram.getZone(String.valueOf(currentProgramMode.getZoneId())); + if (zone != null) { + return new StringType(zone.getName()); + } + return UnDefType.NULL; + } + case AWAY: + case MANUAL: + case FROST_GUARD: + return new StringType(thermMode.name()); + case OFF: + case MAX: + case UNKNOWN: + return UnDefType.UNDEF; + } + return null; + case CHANNEL_SETPOINT_END_TIME: + switch (thermMode) { + case PROGRAM: + case HOME: + case SCHEDULE: + return toDateTimeType(getNextProgramTime(localThing.getActiveProgram()), zoneId); + case AWAY: + case MANUAL: + case FROST_GUARD: + return toDateTimeType(localThing.getThermModeEndTime()); + case OFF: + case MAX: + case UNKNOWN: + return UnDefType.UNDEF; + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeSecurityChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeSecurityChannelHelper.java new file mode 100644 index 0000000000000..214d9198bf43d --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HomeSecurityChannelHelper.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType; + +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.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeSecurity; +import org.openhab.binding.netatmo.internal.api.dto.NAPerson; +import org.openhab.binding.netatmo.internal.api.dto.NAPlace; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.DecimalType; +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; + +/** + * The {@link HomeSecurityChannelHelper} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HomeSecurityChannelHelper extends AbstractChannelHelper { + private final Logger logger = LoggerFactory.getLogger(HomeSecurityChannelHelper.class); + + private int persons = -1; + private int unknowns = -1; + + public HomeSecurityChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_HOME_SECURITY)); + } + + @Override + public void setNewData(NAThing naThing) { + super.setNewData(naThing); + if (naThing instanceof NAHomeSecurity) { + NAHomeSecurity home = (NAHomeSecurity) naThing; + + persons = 0; + unknowns = 0; + + logger.debug("welcome home '{}' counts Persons at home", home.getId()); + + NAObjectMap personList = home.getPersons(); + // if (personList != null) { + List present = personList.values().stream().filter(p -> !p.isOutOfSight()) + .collect(Collectors.toList()); + persons = present.size(); + present = present.stream().filter(p -> p.getName() != null).collect(Collectors.toList()); + unknowns = persons - present.size(); + // } + + } + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + if (CHANNEL_HOME_PERSONCOUNT.equals(channelId)) { + return persons != -1 ? new DecimalType(persons) : UnDefType.UNDEF; + } else if (CHANNEL_HOME_UNKNOWNCOUNT.equals(channelId)) { + return unknowns != -1 ? new DecimalType(unknowns) : UnDefType.UNDEF; + } + NAHome localThing = (NAHome) naThing; + NAPlace place = localThing.getPlace(); + return place == null ? null + : CHANNEL_HOME_CITY.equals(channelId) ? toStringType(place.getCity()) + : CHANNEL_HOME_COUNTRY.equals(channelId) ? toStringType(place.getCountry()) + : CHANNEL_HOME_TIMEZONE.equals(channelId) ? toStringType(place.getTimezone()) : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HumidityChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HumidityChannelHelper.java new file mode 100644 index 0000000000000..1bb2825b9b41e --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/HumidityChannelHelper.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; +import static org.openhab.binding.netatmo.internal.utils.WeatherUtils.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NADashboard; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link HumidityChannelHelper} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HumidityChannelHelper extends AbstractChannelHelper { + + public HumidityChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_HUMIDITY)); + } + + @Override + protected @Nullable State internalGetDashboard(NADashboard dashboard, String channelId) { + return CHANNEL_VALUE.equals(channelId) ? toQuantityType(dashboard.getHumidity(), MeasureClass.HUMIDITY) + : getDerived(dashboard.getTemperature(), dashboard.getHumidity(), channelId); + } + + private @Nullable State getDerived(double temperature, double humidity, String channelId) { + double humidex = getHumidex(temperature, humidity); + switch (channelId) { + case CHANNEL_HUMIDEX: + return new DecimalType(humidex); + case CHANNEL_HUMIDEX_SCALE: + return new DecimalType(humidexScale(humidex)); + case CHANNEL_HEAT_INDEX: + return toQuantityType(getHeatIndex(temperature, humidity), MeasureClass.EXTERIOR_TEMPERATURE); + case CHANNEL_DEWPOINT: + return toQuantityType(getDewPoint(temperature, humidity), MeasureClass.EXTERIOR_TEMPERATURE); + case CHANNEL_DEWPOINT_DEP: + double dewPoint = getDewPoint(temperature, humidity); + return toQuantityType(getDewPointDep(temperature, dewPoint), MeasureClass.EXTERIOR_TEMPERATURE); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/LocationChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/LocationChannelHelper.java new file mode 100644 index 0000000000000..813847ffe3b72 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/LocationChannelHelper.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2021 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.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NADevice; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NAPlace; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.PointType; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link LocationChannelHelper} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class LocationChannelHelper extends AbstractChannelHelper { + + public LocationChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_LOCATION)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + if (CHANNEL_LOCATION.equals(channelId)) { + PointType point = null; + if (naThing instanceof NAHome) { + point = ((NAHome) naThing).getLocation(); + } else if (naThing instanceof NADevice) { + NAPlace place = ((NADevice) naThing).getPlace(); + if (place != null) { + point = place.getLocation(); + } + } + return point != null ? point : UnDefType.UNDEF; + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/MeasuresChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/MeasuresChannelHelper.java new file mode 100644 index 0000000000000..4e27dba47f982 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/MeasuresChannelHelper.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2010-2021 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.utils.ChannelTypeUtils.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureLimit; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.config.MeasureChannelConfig; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class MeasuresChannelHelper extends AbstractChannelHelper { + private final Map measures = new HashMap<>(); + + public MeasuresChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + Channel channel = thing.getChannel(channelId); + if (channel != null) { + Optional config = getChannelConfigIfValid(channel); + if (config.isPresent()) { + MeasureChannelConfig channelConfig = config.get(); + Double measure = measures.get(channelConfig); + if (channelConfig.limit == MeasureLimit.DATE_MAX || channelConfig.limit == MeasureLimit.DATE_MIN) { + return toDateTimeType(measure, zoneId); + } + return toQuantityType(measure, channelConfig.type.getUnit()); + } + } + return null; + } + + private Optional getChannelConfigIfValid(Channel channel) { + MeasureChannelConfig config = channel.getConfiguration().as(MeasureChannelConfig.class); + return config.period != null && config.type != null ? Optional.of(config) : Optional.empty(); + } + + public Map getMeasures() { + return measures; + } + + public void collectMeasuredChannels() { + measures.clear(); + thing.getChannels().stream().map(channel -> getChannelConfigIfValid(channel)).filter(c -> c.isPresent()) + .forEach(config -> { + measures.put(config.get(), Double.NaN); + }); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/NoiseChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/NoiseChannelHelper.java new file mode 100644 index 0000000000000..a0de32aab76cd --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/NoiseChannelHelper.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NADashboard; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link NoiseChannelHelper} handle specific behavior + * of modules measuring sound level + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class NoiseChannelHelper extends AbstractChannelHelper { + + public NoiseChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_NOISE)); + } + + @Override + protected @Nullable State internalGetDashboard(NADashboard dashboard, String channelId) { + 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/channelhelper/PersonChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PersonChannelHelper.java new file mode 100644 index 0000000000000..a22d01fea0f59 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PersonChannelHelper.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAPerson; +import org.openhab.binding.netatmo.internal.api.dto.NASnapshot; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link PersonChannelHelper} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PersonChannelHelper extends AbstractChannelHelper { + + public PersonChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_PERSON)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + NAPerson naPerson = (NAPerson) naThing; + if (CHANNEL_PERSON_AT_HOME.equals(channelId)) { + return OnOffType.from(!naPerson.isOutOfSight()); + } else if (CHANNEL_LAST_SEEN.equals(channelId)) { + return toDateTimeType(naPerson.getLastSeen()); + } + NASnapshot avatar = naPerson.getFace(); + return avatar != null ? internalGetAvatar(avatar, channelId) : null; + } + + private State internalGetAvatar(NASnapshot avatar, String channelId) { + return CHANNEL_PERSON_AVATAR_URL.equals(channelId) ? toStringType(avatar.getUrl()) + : CHANNEL_PERSON_AVATAR.equals(channelId) ? toRawType(avatar.getUrl()) : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PlugChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PlugChannelHelper.java new file mode 100644 index 0000000000000..ac4ccf71d0341 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PlugChannelHelper.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toDateTimeType; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAPlug; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link PlugChannelHelper} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PlugChannelHelper extends AbstractChannelHelper { + + public PlugChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_PLUG)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + if (naThing instanceof NAPlug) { + NAPlug plug = (NAPlug) naThing; + if (CHANNEL_CONNECTED_BOILER.equals(channelId)) { + return plug.getPlugConnectedBoiler(); + } else if (CHANNEL_LAST_BILAN.equals(channelId)) { + return toDateTimeType(plug.getLastBilan()); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PresenceChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PresenceChannelHelper.java new file mode 100644 index 0000000000000..eeb21cbebf73b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PresenceChannelHelper.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2021 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.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.PresenceLightMode; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.api.dto.NAWelcome; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link PresenceChannelHelper} handle specific behavior + * of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PresenceChannelHelper extends AbstractChannelHelper { + private State floodlightAutoMode = UnDefType.UNDEF; + + public PresenceChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_PRESENCE)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + NAWelcome camera = (NAWelcome) naThing; + switch (channelId) { + case CHANNEL_CAMERA_FLOODLIGHT: + return OnOffType.from(camera.getLightModeStatus() == PresenceLightMode.ON); + 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 (floodlightAutoMode == UnDefType.UNDEF) { + floodlightAutoMode = OnOffType.from(camera.getLightModeStatus() == PresenceLightMode.AUTO); + } + return floodlightAutoMode; + } + return null; + } + + public State getAutoMode() { + return floodlightAutoMode; + } + + public void setAutoMode(State mode) { + this.floodlightAutoMode = mode; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PressureChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PressureChannelHelper.java new file mode 100644 index 0000000000000..e10665aacb659 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/PressureChannelHelper.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NADashboard; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link PressureChannelHelper} handle specific behavior + * of modules measuring pressure + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PressureChannelHelper extends AbstractChannelHelper { + + public PressureChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_PRESSURE)); + } + + @Override + protected @Nullable State internalGetDashboard(NADashboard dashboard, String channelId) { + switch (channelId) { + case CHANNEL_VALUE: + return toQuantityType(dashboard.getPressure(), MeasureClass.PRESSURE); + case CHANNEL_TREND: + return toStringType(dashboard.getPressureTrend()); + 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/channelhelper/RadioHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RadioHelper.java deleted file mode 100644 index d1f64507ad54c..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RadioHelper.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 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 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/channelhelper/RainChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RainChannelHelper.java new file mode 100644 index 0000000000000..ec249c05923a3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RainChannelHelper.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NADashboard; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link RainChannelHelper} handle specific behavior + * of modules measuring rain + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class RainChannelHelper extends AbstractChannelHelper { + + public RainChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_RAIN)); + } + + @Override + protected @Nullable State internalGetDashboard(NADashboard dashboard, String channelId) { + switch (channelId) { + case CHANNEL_VALUE: + return toQuantityType(dashboard.getRain(), MeasureClass.RAIN_INTENSITY); + case CHANNEL_SUM_RAIN1: + return toQuantityType(dashboard.getSumRain1(), MeasureClass.RAIN_QTTY); + case CHANNEL_SUM_RAIN24: + return toQuantityType(dashboard.getSumRain24(), MeasureClass.RAIN_QTTY); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomChannelHelper.java new file mode 100644 index 0000000000000..1ca5fc7f59bd2 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomChannelHelper.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NARoom; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link RoomChannelHelper} handle specific behavior + * of the room + * + * @author Markus Dillmann - Initial contribution + * + */ +@NonNullByDefault +public class RoomChannelHelper extends AbstractChannelHelper { + + public RoomChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_ROOM_PROPERTIES)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + NARoom room = (NARoom) naThing; + switch (channelId) { + case CHANNEL_ROOM_WINDOW_OPEN: + return OnOffType.from(room.isOpenWindow()); + case CHANNEL_ANTICIPATING: + return OnOffType.from(room.isAnticipating()); + case CHANNEL_ROOM_HEATING_POWER: + return toQuantityType(room.getHeatingPowerRequest(), Units.PERCENT); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomSetpointChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomSetpointChannelHelper.java new file mode 100644 index 0000000000000..5edbfeed45ef2 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomSetpointChannelHelper.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.api.dto.NARoom; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.i18n.TimeZoneProvider; +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; + +/** + * The {@link RoomSetpointChannelHelper} handle specific behavior + * of a room + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class RoomSetpointChannelHelper extends AbstractChannelHelper { + + public RoomSetpointChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_TH_SETPOINT)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + NARoom room = (NARoom) naThing; + switch (channelId) { + case CHANNEL_VALUE: + return getCurrentSetpoint(room); + case CHANNEL_SETPOINT_MODE: + return new StringType(room.getThermSetpointMode().name()); + case CHANNEL_SETPOINT_START_TIME: + return toDateTimeType(room.getThermSetpointStartTime()); + case CHANNEL_SETPOINT_END_TIME: + return toDateTimeType(room.getThermSetpointEndTime()); + } + return null; + } + + private State getCurrentSetpoint(NARoom room) { + SetpointMode thermSetPointMode = room.getThermSetpointMode(); + switch (thermSetPointMode) { + case AWAY: + case HOME: + case MANUAL: + case SCHEDULE: + case FROST_GUARD: + case PROGRAM: + return toQuantityType(room.getThermSetpointTemperature(), MeasureClass.INTERIOR_TEMPERATURE); + case OFF: + case MAX: + case UNKNOWN: + return UnDefType.UNDEF; + } + return UnDefType.NULL; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomTempChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomTempChannelHelper.java new file mode 100644 index 0000000000000..8120a9e883cd9 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RoomTempChannelHelper.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2021 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.GROUP_ROOM_TEMPERATURE; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NARoom; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link RoomTempChannelHelper} handle specific behavior + * of the thermostat module + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class RoomTempChannelHelper extends AbstractChannelHelper { + + public RoomTempChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_ROOM_TEMPERATURE)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + NARoom room = (NARoom) naThing; + // TODO : are U sure we do not test we return the value for the appropriate channel ??? + return toQuantityType(room.getThermMeasuredTemperature(), MeasureClass.INTERIOR_TEMPERATURE); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/SignalHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/SignalHelper.java new file mode 100644 index 0000000000000..4af12dd2deb01 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/SignalHelper.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2021 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.util.Set; + +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.i18n.TimeZoneProvider; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link SignalHelper} handle specific behavior + * of WIFI or RF devices and modules + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class SignalHelper extends AbstractChannelHelper { + private final int[] levels; + + public SignalHelper(Thing thing, TimeZoneProvider timeZoneProvider, int[] signalLevels) { + super(thing, timeZoneProvider, Set.of(GROUP_SIGNAL)); + this.levels = signalLevels; + } + + private int getSignalStrength(int signalLevel) { + int level; + for (level = 0; level < levels.length; level++) { + if (signalLevel > levels[level]) { + break; + } + } + return level; + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + int status = naThing.getRadioStatus(); + return CHANNEL_SIGNAL_STRENGTH.equals(channelId) ? new DecimalType(getSignalStrength(status)) + : CHANNEL_VALUE.equals(channelId) ? new QuantityType<>(status, Units.DECIBEL_MILLIWATTS) : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/TemperatureChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/TemperatureChannelHelper.java new file mode 100644 index 0000000000000..788986c64d5a1 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/TemperatureChannelHelper.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NADashboard; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link TemperatureChannelHelper} handle specific behavior + * of modules measuring temperature + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class TemperatureChannelHelper extends AbstractChannelHelper { + + public TemperatureChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_TEMPERATURE)); + } + + @Override + protected @Nullable State internalGetDashboard(NADashboard dashboard, String channelId) { + switch (channelId) { + case CHANNEL_VALUE: + return toQuantityType(dashboard.getTemperature(), MeasureClass.EXTERIOR_TEMPERATURE); + case CHANNEL_MIN_VALUE: + return toQuantityType(dashboard.getMinTemp(), MeasureClass.EXTERIOR_TEMPERATURE); + case CHANNEL_MAX_VALUE: + return toQuantityType(dashboard.getMaxTemp(), MeasureClass.EXTERIOR_TEMPERATURE); + case CHANNEL_TREND: + return toStringType(dashboard.getTempTrend()); + case CHANNEL_MIN_TIME: + return toDateTimeType(dashboard.getDateMinTemp()); + case CHANNEL_MAX_TIME: + return toDateTimeType(dashboard.getDateMaxTemp()); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1PropsChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1PropsChannelHelper.java new file mode 100644 index 0000000000000..fd3de31ebf8aa --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1PropsChannelHelper.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAThermostat; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link Therm1PropsChannelHelper} handle specific behavior + * of the thermostat module + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class Therm1PropsChannelHelper extends AbstractChannelHelper { + + public Therm1PropsChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_TH_PROPERTIES)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + NAThermostat thermostat = (NAThermostat) naThing; + switch (channelId) { + case CHANNEL_THERM_RELAY: + return OnOffType.from(thermostat.getThermRelayCmd()); + case CHANNEL_THERM_ORIENTATION: + return toQuantityType((thermostat.getThermOrientation() - 1) * 90, Units.DEGREE_ANGLE); + case CHANNEL_ANTICIPATING: + return OnOffType.from(thermostat.isAnticipating()); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1SetpointChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1SetpointChannelHelper.java new file mode 100644 index 0000000000000..b06213f9e72e2 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1SetpointChannelHelper.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; +import static org.openhab.binding.netatmo.internal.utils.NetatmoCalendarUtils.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.ThermostatZoneType; +import org.openhab.binding.netatmo.internal.api.dto.NAThermProgram; +import org.openhab.binding.netatmo.internal.api.dto.NAThermostat; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.api.dto.NATimeTableItem; +import org.openhab.core.i18n.TimeZoneProvider; +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; + +/** + * The {@link Therm1SetpointChannelHelper} handle specific behavior + * of the thermostat module + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class Therm1SetpointChannelHelper extends AbstractChannelHelper { + + public Therm1SetpointChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_TH_SETPOINT)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + NAThermostat thermostat = (NAThermostat) naThing; + switch (channelId) { + case CHANNEL_VALUE: + return getCurrentSetpoint(thermostat); + case CHANNEL_SETPOINT_END_TIME: + long endTime = thermostat.getSetpointEndtime(); + return toDateTimeType(endTime != 0 ? endTime : getNextProgramTime(thermostat.getActiveProgram()), + zoneId); + case CHANNEL_SETPOINT_MODE: + return new StringType(thermostat.getSetpointMode().name()); + } + return null; + } + + private State getCurrentSetpoint(NAThermostat thermostat) { + SetpointMode currentMode = thermostat.getSetpointMode(); + NAThermProgram currentProgram = thermostat.getActiveProgram(); + switch (currentMode) { + case PROGRAM: + NATimeTableItem currentProgramMode = getCurrentProgramMode(thermostat.getActiveProgram()); + if (currentProgram != null && currentProgramMode != null) { + ThermostatZoneType zoneType = currentProgramMode.getZoneType(); + return toQuantityType(currentProgram.getZoneTemperature(zoneType), + MeasureClass.INTERIOR_TEMPERATURE); + } + case AWAY: + case FROST_GUARD: + return toQuantityType(currentProgram != null ? currentProgram.getZoneTemperature(currentMode) : null, + MeasureClass.INTERIOR_TEMPERATURE); + case SCHEDULE: + case HOME: + case MANUAL: + return toQuantityType(thermostat.getSetpointTemp(), MeasureClass.INTERIOR_TEMPERATURE); + case OFF: + case MAX: + case UNKNOWN: + return UnDefType.UNDEF; + } + return UnDefType.NULL; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1TempChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1TempChannelHelper.java new file mode 100644 index 0000000000000..c01591104f22c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/Therm1TempChannelHelper.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NAThermMeasure; +import org.openhab.binding.netatmo.internal.api.dto.NAThermostat; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link Therm1TempChannelHelper} handle specific behavior + * of the thermostat module + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class Therm1TempChannelHelper extends AbstractChannelHelper { + + public Therm1TempChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_TH_TEMPERATURE)); + } + + @Override + protected @Nullable State internalGetProperty(NAThing naThing, String channelId) { + NAThermostat thermostat = (NAThermostat) naThing; + NAThermMeasure measured = thermostat.getMeasured(); + if (measured != null && CHANNEL_VALUE.equals(channelId)) { + return toQuantityType(measured.getTemperature(), MeasureClass.EXTERIOR_TEMPERATURE); + } else if (measured != null && CHANNEL_TIMEUTC.equals(channelId)) { + return toDateTimeType(measured.getTime()); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/WindChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/WindChannelHelper.java new file mode 100644 index 0000000000000..7e89741a9246c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/WindChannelHelper.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2021 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 static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NADashboard; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.State; + +/** + * The {@link WindChannelHelper} handle specific behavior + * of modules measuring wind + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class WindChannelHelper extends AbstractChannelHelper { + + public WindChannelHelper(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider, Set.of(GROUP_WIND)); + } + + @Override + protected @Nullable State internalGetDashboard(NADashboard dashboard, String channelId) { + 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/config/MeasureChannelConfig.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/MeasureChannelConfig.java new file mode 100644 index 0000000000000..8d897757546c7 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/MeasureChannelConfig.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2021 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.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureLimit; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureScale; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureType; + +/** + * The {@link MeasureChannelConfig} holds configuration parameters + * for extensible channels + * + * @author Gaël L'hopital - Initial contribution + * + */ +public class MeasureChannelConfig { + public MeasureScale period; + public MeasureType type; + public MeasureLimit limit = MeasureLimit.NONE; + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((limit == null) ? 0 : limit.hashCode()); + result = prime * result + ((period == null) ? 0 : period.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MeasureChannelConfig other = (MeasureChannelConfig) obj; + if (limit != other.limit) { + return false; + } + if (period != other.period) { + return false; + } + if (type != other.type) { + return false; + } + return true; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBindingConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBindingConfiguration.java new file mode 100644 index 0000000000000..7464774d81dd4 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBindingConfiguration.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2021 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 NetatmoBindingConfiguration} is responsible for holding configuration + * informations needed to access Netatmo API and general binding behavior setup + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NetatmoBindingConfiguration { + public @Nullable String clientId; + public @Nullable String clientSecret; + public @Nullable String username; + public @Nullable String password; + public @Nullable String webHookUrl; + public int reconnectInterval = 5400; + private boolean backgroundDiscovery; + + public void update(NetatmoBindingConfiguration newConfiguration) { + this.clientId = newConfiguration.clientId; + this.clientSecret = newConfiguration.clientSecret; + this.username = newConfiguration.username; + this.password = newConfiguration.password; + this.webHookUrl = newConfiguration.webHookUrl; + this.reconnectInterval = newConfiguration.reconnectInterval; + this.backgroundDiscovery = newConfiguration.backgroundDiscovery; + } + + public void checkIfValid() throws NetatmoException { + String clientId = this.clientId; + if (clientId == null || clientId.isEmpty()) { + throw new NetatmoException("@text/conf-error-no-client-id"); + } + String username = this.username; + if (username == null || username.isEmpty()) { + throw new NetatmoException("@text/conf-error-no-username"); + } + String password = this.password; + if (password == null || password.isEmpty()) { + throw new NetatmoException("@text/conf-error-no-password"); + } + String clientSecret = this.clientSecret; + if (clientSecret == null || clientSecret.isEmpty()) { + throw new NetatmoException("@text/conf-error-no-client-secret"); + } + } +} 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 index f696ce87f051f..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBridgeConfiguration.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2010-2021 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/config/NetatmoThingConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoThingConfiguration.java new file mode 100644 index 0000000000000..54fd8286493c5 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoThingConfiguration.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2021 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 NetatmoThingConfiguration} is responsible for holding + * configuration informations for any Netatmo thing module or device + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NetatmoThingConfiguration { + public @NonNullByDefault({}) String id; + public int refreshInterval = -1; +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADynamicObjectMap.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADynamicObjectMap.java new file mode 100644 index 0000000000000..b3293cade4bc3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADynamicObjectMap.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2021 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; + +/** + * The {@link NADynamicObjectMap} defines an hashmap of NAObjects identified + * by their id, dynamically created upon API response + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NADynamicObjectMap extends HashMap { + private static final long serialVersionUID = -7864636414965562293L; + + // TODO Remove unused code found by UCDetector + // public List forModuleType(ModuleType searchedType) { + // List result = new ArrayList<>(); + // result.addAll(values().stream().filter(thing -> thing.getType() == searchedType).collect(Collectors.toList())); + // return result; + // } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADynamicObjectMapDeserializer.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADynamicObjectMapDeserializer.java new file mode 100644 index 0000000000000..9f84f700f40c5 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADynamicObjectMapDeserializer.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2021 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.ModuleType; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +/** + * The {@link NADynamicObjectMapDeserializer} 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 +public class NADynamicObjectMapDeserializer implements JsonDeserializer { + private final Logger logger = LoggerFactory.getLogger(NADynamicObjectMapDeserializer.class); + + @Override + public @Nullable NADynamicObjectMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + if (json instanceof JsonArray) { + NADynamicObjectMap result = new NADynamicObjectMap(); + for (JsonElement item : (JsonArray) json) { + JsonObject jsonO = item.getAsJsonObject(); + String thingType = jsonO.get("type").getAsString(); + if (ModuleType.isModuleTypeImplemented(thingType)) { + ModuleType module = ModuleType.valueOf(thingType); + Class dto = module.getDto(); + if (dto != null) { + NAThing obj = context.deserialize(item, dto); + result.put(obj.getId(), obj); + } else { + logger.warn("Unable to find appropriate dto for thing of type : {}", thingType); + } + } else { + logger.warn("unsupported moduletype {} found during discovery", thingType); + } + } + return result; + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAHomeDeserializer.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAHomeDeserializer.java new file mode 100644 index 0000000000000..21a860fa8e7d3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAHomeDeserializer.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2021 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.ModuleType; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeEnergy; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeSecurity; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeWeather; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +/** + * Specialized deserializer NAHome + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NAHomeDeserializer implements JsonDeserializer { + + @Override + public @Nullable NAHome deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + + NAHomeWeather result = context.deserialize(json, NAHomeWeather.class); + if (result.containsModuleType(ModuleType.NAPlug)) { + NAHomeEnergy homeEnergy = context.deserialize(json, NAHomeEnergy.class); + homeEnergy.setType(ModuleType.NAHomeEnergy); + return homeEnergy; + } + if (result.containsModuleType(ModuleType.NACamera) || result.containsModuleType(ModuleType.NOC)) { + // This way of detecting home kinds presents a problem if no module is installed then everything will fall + // as a HomeWeather + NAHomeSecurity homeSec = context.deserialize(json, NAHomeSecurity.class); + homeSec.getModules().putAll(homeSec.getPersons()); + homeSec.getModules().putAll(homeSec.getCameras()); + homeSec.setType(ModuleType.NAHomeSecurity); + return homeSec; + } + result.setType(ModuleType.NAHomeWeather); + return result; + } +} 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 index 0000000000000..2dac08c85b0f4 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2010-2021 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; + +/** + * The {@link NAObjectMap} defines an hashmap of NAObjects identified + * by their id. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NAObjectMap extends HashMap { + private static final long serialVersionUID = 7635233672795516649L; +} 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 index 0000000000000..eb24f702c7cf2 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMapDeserializer.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2021 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 +public class NAObjectMapDeserializer implements JsonDeserializer> { + @Override + public @Nullable NAObjectMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + ParameterizedType parameterized = (ParameterizedType) typeOfT; + Type[] typeArguments = parameterized.getActualTypeArguments(); + if (typeArguments.length > 0 && json instanceof JsonArray) { + Type objectType = typeArguments[0]; + NAObjectMap result = new NAObjectMap<>(); + for (JsonElement item : (JsonArray) json) { + NAObject obj = context.deserialize(item, objectType); + result.put(obj.getId(), obj); + } + 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 index 0000000000000..0a7f1b27569f8 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushType.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2021 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.EventType; +import org.openhab.binding.netatmo.internal.api.ModuleType; + +/** + * This class holds informations of push_type field + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NAPushType { + private final ModuleType moduleType; + private final EventType event; + + public 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 index 0000000000000..e6d5a9d08ec04 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushTypeDeserializer.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2021 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.EventType; +import org.openhab.binding.netatmo.internal.api.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 +public class NAPushTypeDeserializer implements JsonDeserializer { + + @Override + public @Nullable NAPushType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + + String string = json.getAsString(); + String[] elements = string.split("-"); + if (elements.length > 1) { + ModuleType moduleType = ModuleType.valueOf(elements[0]); + EventType eventType = EventType.valueOf(elements[1].toUpperCase()); + + return new NAPushType(moduleType, eventType); + } + return null; + } +} 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 index 0000000000000..b26d7ea757692 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/StrictEnumTypeAdapterFactory.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2021 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; + +/** + * This 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 +public class StrictEnumTypeAdapterFactory implements TypeAdapterFactory { + + @Override + public @Nullable TypeAdapter create(@NonNullByDefault({}) Gson gson, + @NonNullByDefault({}) TypeToken type) { + @SuppressWarnings("unchecked") + Class rawType = (Class) type.getRawType(); + if (!rawType.isEnum()) { + return null; + } + return newStrictEnumAdapter(gson.getDelegateAdapter(this, type)); + } + + private TypeAdapter newStrictEnumAdapter(final TypeAdapter delegateAdapter) { + return new TypeAdapter() { + + @Override + public void write(JsonWriter out, @Nullable T value) throws IOException { + delegateAdapter.write(out, value); + } + + @Override + public @Nullable T read(JsonReader in) throws IOException { + String enumValue = in.nextString(); + JsonReader delegateReader = new JsonReader(new StringReader('"' + enumValue + '"')); + T value = delegateAdapter.read(delegateReader); + if (value == null) { + value = delegateAdapter.read(new JsonReader(new StringReader("\"UNKNOWN\""))); + } + delegateReader.close(); + 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 index 0000000000000..15415f9a16c8f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoDiscoveryService.java @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2010-2021 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.HashSet; +import java.util.List; +import java.util.Set; +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.ApiBridge; +import org.openhab.binding.netatmo.internal.api.ConnectionListener; +import org.openhab.binding.netatmo.internal.api.ConnectionStatus; +import org.openhab.binding.netatmo.internal.api.ModuleType; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.WeatherApi.NAStationDataResponse; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NAMain; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +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.i18n.LocaleProvider; +import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +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; + +/** + * The {@link NetatmoDiscoveryService} searches for available Netatmo + * devices and modules connected to the API console + * + * @author Gaël L'hopital - Initial contribution + * + */ +@Component(service = DiscoveryService.class, configurationPid = "binding.netatmo") +@NonNullByDefault +public class NetatmoDiscoveryService extends AbstractDiscoveryService implements ConnectionListener { + private static final int DISCOVER_TIMEOUT_SECONDS = 5; + private final Logger logger = LoggerFactory.getLogger(NetatmoDiscoveryService.class); + private final ApiBridge apiBridge; + + @Activate + public NetatmoDiscoveryService(@Reference ApiBridge apiBridge, @Reference LocaleProvider localeProvider, + @Reference TranslationProvider translationProvider) { + + super(Stream.of(ModuleType.values()).map(supported -> supported.getThingTypeUID()).collect(Collectors.toSet()), + DISCOVER_TIMEOUT_SECONDS); + this.apiBridge = apiBridge; + this.localeProvider = localeProvider; + this.i18nProvider = translationProvider; + apiBridge.addConnectionListener(this); + } + + @Override + public void notifyStatusChange(ConnectionStatus connectionStatus) { + if (connectionStatus.isConnected()) { + super.activate(null); + } else { + super.deactivate(); + } + } + + @Override + public void startScan() { + try { + List result = apiBridge.getHomeApi().getHomes(null); // .getHomeApi().getHomeList(null); + Set<@Nullable String> roomsWithEnergyModules = new HashSet<>(); + result.forEach(home -> { + ThingUID homeUID = createDiscoveredThing(null, home, home.getType()); + home.getModules().values().stream().filter(module -> module.getBridge() == null) + .forEach(foundBridge -> { + ThingUID bridgeUID = createDiscoveredThing(homeUID, foundBridge, foundBridge.getType()); + home.getModules().values().stream() + .filter(module -> foundBridge.getId().equalsIgnoreCase(module.getBridge())) + .forEach(foundChild -> { + createDiscoveredThing(bridgeUID, foundChild, foundChild.getType()); + if ((foundChild.getType() == ModuleType.NRV + || foundChild.getType() == ModuleType.NATherm1) + && (foundChild.getRoomId() != null)) { + roomsWithEnergyModules.add(foundChild.getRoomId()); + } + }); + }); + // Not sure this is still needed, NAHomeDeserializer adds persons in the module list + // will this work ? /homesdata from Energy-API won't return security-info (needs /gethomedata) + // if (home instanceof NAHomeSecurity) { + // NAHomeSecurity homesec = (NAHomeSecurity) home; + // List persons = homesec.getKnownPersons(); + // persons.forEach(person -> createDiscoveredThing(homeUID, person, person.getType())); + // } + // mark energy-modules that are assigned to a room + // Are or should modules be childs of their room ? + // only create NARoom for energy-modules for now + home.getRooms().stream().filter(r -> roomsWithEnergyModules.contains(r.getId())) + .forEach(room -> createDiscoveredThing(homeUID, room, room.getType())); + }); + + // Get favorites weather stations : they are readonly + apiBridge.getWeatherApi().ifPresent(weatherApi -> { + try { + NAStationDataResponse stations = weatherApi.getStationsData(null, true); + stations.getBody().getDevices().values().stream().filter(NAMain::isReadOnly).forEach(station -> { + createDiscoveredThing(null, station, station.getType()); + station.getModules().values().stream().filter(module -> module.getBridge() == null) + .forEach(foundBridge -> { + ThingUID bridgeUID = createDiscoveredThing(null, foundBridge, + foundBridge.getType()); + station.getModules().values().stream() + .filter(module -> foundBridge.getId().equalsIgnoreCase(module.getBridge())) + .forEach(foundChild -> { + createDiscoveredThing(bridgeUID, foundChild, foundChild.getType()); + }); + }); + }); + } catch (NetatmoException e) { + logger.warn("Error getting stations", e); + } + }); + } catch ( + + NetatmoException e) { + logger.warn("Error getting Home List", e); + } + // apiBridge.getAirCareApi().ifPresent(api -> searchHomeCoach(api)); + } + + private ThingUID findThingUID(ModuleType thingType, String thingId, @Nullable ThingUID brigdeUID) + throws IllegalArgumentException { + for (ThingTypeUID supported : getSupportedThingTypes()) { + if (supported.getId().equalsIgnoreCase(thingType.name())) { + String id = thingId.replaceAll("[^a-zA-Z0-9_]", ""); + if (brigdeUID == null) { + return new ThingUID(supported, id); + } + return new ThingUID(supported, brigdeUID, id); + } + } + throw new IllegalArgumentException("Unsupported device type discovered : " + thingType); + } + + private @Nullable ThingUID createDiscoveredThing(@Nullable ThingUID bridgeUID, NAThing module, + ModuleType moduleType) { + ThingUID moduleUID = null; + + if (((moduleType.getBridgeThingType() == null && bridgeUID == null) + || (moduleType.getBridgeThingType() != null && bridgeUID != null)) + && moduleType != ModuleType.NAHomeWeather) { // NAHomeWeather should not appear + moduleUID = findThingUID(moduleType, module.getId(), bridgeUID); + DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(moduleUID) + .withProperty(EQUIPMENT_ID, module.getId()) + .withLabel(module.getName() != null ? module.getName() : module.getId()) + .withRepresentationProperty(EQUIPMENT_ID); + if (bridgeUID != null) { + resultBuilder = resultBuilder.withBridge(bridgeUID); + } + thingDiscovered(resultBuilder.build()); + } + return moduleUID; + } + + // Normally home coach should be discovered by home discovery but not 100% sure, kept for the moment. + // private void searchHomeCoach(AircareApi api) { + // try { + // NADeviceDataBody result = api.getHomeCoachDataBody(null); + // for (NAMain homeCoach : result.getDevices().values()) { + // discoverHomeCoach(homeCoach); + // } + // } catch (NetatmoException e) { + // logger.warn("Error retrieving thermostat(s)", e); + // } + // } +} 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 index 7454cb92ac540..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryService.java +++ /dev/null @@ -1,253 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 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 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 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 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 index 6139dacc58c57..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/AbstractNetatmoThingHandler.java +++ /dev/null @@ -1,268 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 API_TEMPERATURE_UNIT = SIUnits.CELSIUS; - public static final Unit API_HUMIDITY_UNIT = Units.PERCENT; - public static final Unit API_PRESSURE_UNIT = HECTO(SIUnits.PASCAL); - public static final Unit API_WIND_SPEED_UNIT = SIUnits.KILOMETRE_PER_HOUR; - public static final Unit API_WIND_DIRECTION_UNIT = Units.DEGREE_ANGLE; - public static final Unit API_RAIN_UNIT = MILLI(SIUnits.METRE); - public static final Unit API_CO2_UNIT = Units.PARTS_PER_MILLION; - public static final Unit 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 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 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 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 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 getRadioHelper() { - RadioHelper helper = radioHelper; - return helper != null ? Optional.of(helper) : Optional.empty(); - } - - protected Optional 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 types, - List channels, Map channelMeasurements) { - Optional handler = getBridgeHandler(); - if (!handler.isPresent() || device == null) { - return; - } - - if (types.size() != channels.size()) { - throw new IllegalArgumentException("types and channels lists are different sizes."); - } - - List 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 channels, List 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/camera/CameraAddress.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CameraAddress.java similarity index 97% rename from bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraAddress.java rename to bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CameraAddress.java index 7a1b88eba66fd..d695df0e4f621 100644 --- 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/handler/CameraAddress.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.netatmo.internal.camera; +package org.openhab.binding.netatmo.internal.handler; import java.util.Objects; diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CameraHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CameraHandler.java new file mode 100644 index 0000000000000..9c17ef326ab93 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CameraHandler.java @@ -0,0 +1,169 @@ +/** + * Copyright (c) 2010-2021 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.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +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.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.dto.NAEvent; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeEvent; +import org.openhab.binding.netatmo.internal.api.dto.NASnapshot; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.api.dto.NAWelcome; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@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 class CameraHandler extends NetatmoDeviceHandler { + private final Logger logger = LoggerFactory.getLogger(CameraHandler.class); + private @Nullable CameraAddress cameraAddress; + private @Nullable String vpnUrl; + private boolean isLocal; + private ZonedDateTime maxEventTime; + + public CameraHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + String lastEvent = editProperties().get(PROPERTY_MAX_EVENT_TIME); + maxEventTime = lastEvent != null ? ZonedDateTime.parse(lastEvent) : Instant.EPOCH.atZone(ZoneOffset.UTC); + } + + private @Nullable HomeSecurityHandler getHomeHandler() { + NetatmoDeviceHandler handler = super.getBridgeHandler(getBridge()); + return handler != null ? (HomeSecurityHandler) handler : null; + } + + @Override + public void setNAThing(NAThing naModule) { + super.setNAThing(naModule); + NAWelcome camera = (NAWelcome) naModule; + this.vpnUrl = camera.getVpnUrl(); + this.isLocal = camera.isLocal(); + HomeSecurityHandler homeHandler = getHomeHandler(); + if (homeHandler != null) { + descriptionProvider.setStateOptions( + new ChannelUID(getThing().getUID(), GROUP_WELCOME_EVENT, CHANNEL_EVENT_PERSON_ID), + homeHandler.getKnownPersons().stream().map(p -> new StateOption(p.getId(), p.getName())) + .collect(Collectors.toList())); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if ((command instanceof OnOffType) && (CHANNEL_CAMERA_IS_MONITORING.equals(channelUID.getIdWithoutGroup()))) { + String localCameraURL = getLocalCameraURL(); + if (localCameraURL != null) { + tryApiCall(() -> apiBridge.getHomeApi().changeStatus(localCameraURL, command == OnOffType.ON)); + } + } else { + super.handleCommand(channelUID, command); + } + } + + protected @Nullable String getLocalCameraURL() { + CameraAddress address = cameraAddress; + String vpn = vpnUrl; + if (vpn != null) { + // The local address is (re-)requested when it wasn't already determined or when + // the vpn address was changed. + if (address == null || address.isVpnURLChanged(vpn)) { + String localUrl = pingVpnUrl(vpn); + if (localUrl != null) { + address = new CameraAddress(vpn, localUrl); + cameraAddress = address; + return address.getLocalURL(); + } + } else { + return address.getLocalURL(); + } + } + return null; + } + + @Override + public void setEvent(NAEvent event) { + if (event.getTime().isAfter(maxEventTime)) { + maxEventTime = event.getTime(); + updateProperty(PROPERTY_MAX_EVENT_TIME, maxEventTime.toString()); + + logger.debug("Updating camera with event : {}", event.toString()); + updateIfLinked(GROUP_WELCOME_EVENT, CHANNEL_EVENT_TYPE, toStringType(event.getEventType())); + updateIfLinked(GROUP_WELCOME_EVENT, CHANNEL_EVENT_MESSAGE, toStringType(event.getMessage())); + updateIfLinked(GROUP_WELCOME_EVENT, CHANNEL_EVENT_TIME, new DateTimeType(event.getTime())); + updateIfLinked(GROUP_WELCOME_EVENT, CHANNEL_EVENT_PERSON_ID, toStringType(event.getPersonId())); + updateIfLinked(GROUP_WELCOME_EVENT, CHANNEL_EVENT_SUBTYPE, + event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL)); + + NASnapshot snapshot = event.getSnapshot(); + if (snapshot != null) { + String url = snapshot.getUrl(); + updateIfLinked(GROUP_WELCOME_EVENT, CHANNEL_EVENT_SNAPSHOT, toRawType(url)); + updateIfLinked(GROUP_WELCOME_EVENT, CHANNEL_EVENT_SNAPSHOT_URL, toStringType(url)); + } + if (event instanceof NAHomeEvent) { + NAHomeEvent homeEvent = (NAHomeEvent) event; + updateIfLinked(GROUP_WELCOME_EVENT, CHANNEL_EVENT_VIDEO_STATUS, + toStringType(homeEvent.getVideoStatus())); + updateIfLinked(GROUP_WELCOME_EVENT, CHANNEL_EVENT_VIDEO_URL, + toStringType(getStreamURL(homeEvent.getVideoId()))); + } + } + } + + private @Nullable String getStreamURL(@Nullable String videoId) { + if (videoId != null && vpnUrl != null) { + StringBuilder result = new StringBuilder(String.format("%s/vod/%s/index", vpnUrl, videoId)); + if (isLocal) { + result.append("_local"); + } + result.append(".m3u8"); + return result.toString(); + } + return null; + } + + private @Nullable String pingVpnUrl(String vpnUrl) { + try { + return apiBridge.getHomeApi().ping(vpnUrl); + } catch (NetatmoException e) { + logger.warn("Error pinging camera : {}", e.getMessage()); + return null; + } + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeCoachHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeCoachHandler.java new file mode 100644 index 0000000000000..323b253d13bdc --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeCoachHandler.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2021 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.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.AircareApi; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.dto.NAMain; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.core.thing.Bridge; + +/** + * {@link HomeCoachHandler} is the class used to handle the Health Home Coach device + * + * @author Michael Svinth - Initial contribution + * + */ +@NonNullByDefault +public class HomeCoachHandler extends NetatmoDeviceHandler { + + public HomeCoachHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + } + + @Override + protected NAMain updateReadings() throws NetatmoException { + AircareApi api = apiBridge.getRestManager(AircareApi.class); + if (api != null) { + return api.getHomeCoachData(config.id); + } + throw new NetatmoException("No restmanager available for Air Care access"); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeEnergyHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeEnergyHandler.java new file mode 100644 index 0000000000000..fa1c826e578a9 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeEnergyHandler.java @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2010-2021 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.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.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.EnergyApi; +import org.openhab.binding.netatmo.internal.api.HomeApi; +import org.openhab.binding.netatmo.internal.api.ModuleType; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeEnergy; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link HomeEnergyHandler} is the class used to handle the plug + * device of a thermostat set + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HomeEnergyHandler extends NetatmoDeviceHandler { + private final Logger logger = LoggerFactory.getLogger(HomeEnergyHandler.class); + private NAHome home = new NAHomeEnergy(); + + public HomeEnergyHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + } + + @Override + protected NAHome updateReadings() throws NetatmoException { + EnergyApi api = apiBridge.getRestManager(EnergyApi.class); + HomeApi homeapi = apiBridge.getRestManager(HomeApi.class); + if (api != null && homeapi != null) { + home = homeapi.getHomeList(config.id, ModuleType.NAPlug).get(0); + if (home instanceof NAHomeEnergy) { + NAHomeEnergy homeEnergy = (NAHomeEnergy) home; + NAHome status = api.getHomeStatus(config.id); + // could not find out how to persist retrieved /homesdata and /homestatus so that the information later + // is accesssible by the other handlers + home.setRooms(status.getRooms()); + home.setModules(status.getModules()); + ChannelUID channelUID = new ChannelUID(getThing().getUID(), GROUP_HOME_ENERGY, CHANNEL_PLANNING); + descriptionProvider.setStateOptions(channelUID, homeEnergy.getThermSchedules().stream() + .map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList())); + return home; + } + } + throw new NetatmoException("No api available to access Energy or Home Api"); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + super.handleCommand(channelUID, command); + } else { + String channelName = channelUID.getIdWithoutGroup(); + if (CHANNEL_PLANNING.equals(channelName)) { + apiBridge.getEnergyApi().ifPresent(api -> { + tryApiCall(() -> api.switchSchedule(config.id, command.toString())); + }); + } else if (channelName.equals(CHANNEL_SETPOINT_MODE)) { + SetpointMode targetMode = SetpointMode.valueOf(command.toString()); + if (targetMode == SetpointMode.MANUAL) { + // updateState(channelUID, toStringType(currentData.getSetpointMode())); + logger.info("Switch to 'Manual' is done by setting a setpoint temp, command ignored"); + } else { + callSetThermMode(config.id, targetMode); + } + } + } + } + + @Override + protected void updateChildModules() { + super.updateChildModules(); + if (naThing instanceof NAHomeEnergy) { + NAHomeEnergy localNaThing = (NAHomeEnergy) naThing; + localNaThing.getRooms().forEach(entry -> notifyListener(entry.getId(), entry)); + } + } + + private void callSetThermMode(String homeId, SetpointMode targetMode) { + apiBridge.getEnergyApi().ifPresent(api -> { + tryApiCall(() -> api.setThermMode(homeId, targetMode.getDescriptor())); + }); + } + + public int getSetpointDefaultDuration() { + NAHomeEnergy localHome = getHome(); + if (localHome != null) { + return localHome.getThermSetpointDefaultDuration(); + } + return -1; + } + + public @Nullable NAHomeEnergy getHome() { + if (home instanceof NAHomeEnergy) { + return (NAHomeEnergy) home; + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeSecurityHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeSecurityHandler.java new file mode 100644 index 0000000000000..18c4262cbf156 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/HomeSecurityHandler.java @@ -0,0 +1,187 @@ +/** + * Copyright (c) 2010-2021 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.PROPERTY_MAX_EVENT_TIME; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.HomeApi; +import org.openhab.binding.netatmo.internal.api.ModuleType; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.dto.NAEvent; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeEvent; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeSecurity; +import org.openhab.binding.netatmo.internal.api.dto.NAPerson; +import org.openhab.binding.netatmo.internal.api.dto.NAWebhookEvent; +import org.openhab.binding.netatmo.internal.api.dto.NAWelcome; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link HomeSecurityHandler} is the class used to handle the plug + * device of a thermostat set + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HomeSecurityHandler extends NetatmoDeviceHandler { + private final Logger logger = LoggerFactory.getLogger(HomeSecurityHandler.class); + + private @Nullable NetatmoServlet webhookServlet; + private ZonedDateTime maxEventTime; + private List knownPersons = List.of(); + private NAObjectMap cameras = new NAObjectMap(); + + public HomeSecurityHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + String lastEvent = editProperties().get(PROPERTY_MAX_EVENT_TIME); + maxEventTime = lastEvent != null ? ZonedDateTime.parse(lastEvent) : Instant.EPOCH.atZone(ZoneOffset.UTC); + } + + @Override + public void initialize() { + super.initialize(); + NetatmoServlet servlet = this.webhookServlet; + if (servlet != null) { + servlet.registerDataListener(config.id, this); + } + } + + @Override + protected NAHome updateReadings() throws NetatmoException { + HomeApi api = apiBridge.getRestManager(HomeApi.class); + if (api != null) { + NAHome home = api.getHomes(config.id).get(0); + if (home instanceof NAHomeSecurity) { + this.knownPersons = ((NAHomeSecurity) home).getKnownPersons(); + this.cameras = ((NAHomeSecurity) home).getCameras(); + return home; + } + } + throw new NetatmoException("No api available to access Welcome Home"); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + super.handleCommand(channelUID, command); + } else { + + } + } + + @Override + protected void updateChildModules() { + super.updateChildModules(); + if (naThing instanceof NAHomeSecurity) { + NAHomeSecurity localNaThing = (NAHomeSecurity) naThing; + // localNaThing.getCameras().forEach(entry -> notifyListener(entry.getId(), entry)); + // NAObjectMap persons = localNaThing.getPersons(); + // if (persons != null) { + // persons.entrySet().forEach(entry -> notifyListener(entry.getKey(), entry.getValue())); + // } + localNaThing.getEvents().stream().filter(e -> e.getTime().isAfter(maxEventTime)) + .sorted(Comparator.comparing(NAHomeEvent::getTime)).forEach(event -> { + String personId = event.getPersonId(); + if (personId != null) { + notifyListener(personId, event); + } + notifyListener(event.getCameraId(), event); + maxEventTime = event.getTime(); + }); + + updateProperty(PROPERTY_MAX_EVENT_TIME, maxEventTime.toString()); + } + } + + public void setWebHookServlet(NetatmoServlet servlet) { + this.webhookServlet = servlet; + } + + @Override + public void dispose() { + NetatmoServlet servlet = this.webhookServlet; + if (servlet != null) { + servlet.unregisterDataListener(this); + } + super.dispose(); + } + + @Override + public void setEvent(NAEvent event) { + if (event instanceof NAWebhookEvent) { + NAWebhookEvent whEvent = (NAWebhookEvent) event; + Set modules = new HashSet<>(); + if (whEvent.getEventType().appliesOn(ModuleType.NACamera) + || whEvent.getEventType().appliesOn(ModuleType.NOC)) { + modules.add(whEvent.getCameraId()); + } + if (event.getEventType().appliesOn(ModuleType.NAPerson)) { + modules.addAll(whEvent.getPersons().keySet()); + } + modules.forEach(module -> notifyListener(module, whEvent)); + } else { + super.setEvent(event); + } + } + + public void callSetPersonAway(String personId, boolean away) { + if (away) { + tryApiCall(() -> apiBridge.getHomeApi().setpersonsaway(config.id, personId)); + } else { + tryApiCall(() -> apiBridge.getHomeApi().setpersonshome(config.id, personId)); + } + } + + public List getKnownPersons() { + return this.knownPersons; + } + + public NAObjectMap getCameras() { + return this.cameras; + } + + public List getLastEventOf(String personId) { + List events = new ArrayList<>(); + apiBridge.getSecurityApi().ifPresent(api -> { + try { + events.addAll(api.getLastEventOf(config.id, personId)); + } catch (NetatmoException e) { + logger.warn("Error retrieving last events of person '{}' : {}", personId, e.getMessage()); + } + }); + return events; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/MainHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/MainHandler.java new file mode 100644 index 0000000000000..80af094cb43fa --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/MainHandler.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2021 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.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.WeatherApi; +import org.openhab.binding.netatmo.internal.api.dto.NAMain; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.core.thing.Bridge; + +/** + * {@link MainHandler} is the base class for all current Netatmo + * weather station equipments (both modules and devices) + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class MainHandler extends NetatmoDeviceHandler { + + public MainHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + } + + @Override + protected NAMain updateReadings() throws NetatmoException { + WeatherApi api = apiBridge.getRestManager(WeatherApi.class); + if (api != null) { + return api.getStationData(config.id); + } + throw new NetatmoException("No restmanager available for Weather access"); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NRVHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NRVHandler.java new file mode 100644 index 0000000000000..1189f90a18795 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NRVHandler.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2021 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.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NAModule; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingStatus; + +/** + * {@link NRVHandler} is the class used to handle the valve + * module of a thermostat set + * + * @author Gaël L'hopital - Initial contribution + * + */ +@SuppressWarnings("unused") +@NonNullByDefault +public class NRVHandler extends NetatmoDeviceHandler { + + public NRVHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + } + + private @NonNullByDefault({}) HomeEnergyHandler getHomeHandler() { + Bridge bridge = super.getBridge(); + if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) { + PlugHandler plughandler = (PlugHandler) bridge.getHandler(); + if (plughandler != null) { + return plughandler.getHomeHandler(); + } + } + return null; + } + + @Override + protected NAModule updateReadings() throws NetatmoException { + NAHome localHome = getHomeHandler().getHome(); + if (localHome != null) { + return (NAModule) Objects.requireNonNullElse(localHome.getModule(config.id), new NAModule()); + } + return new NAModule(); + } +} 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 index 61ce9fa850f94..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoBridgeHandler.java +++ /dev/null @@ -1,408 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 dataListeners = new CopyOnWriteArrayList<>(); - - private static class APICreator { - - private final ApiClient apiClient; - private final Map, Object> apiMap; - - private APICreator(ApiClient apiClient) { - super(); - this.apiClient = apiClient; - apiMap = new HashMap<>(); - } - - @SuppressWarnings("unchecked") - public T getAPI(Class 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 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 getStationApi() { - return apiCreator != null ? Optional.of(apiCreator.getAPI(StationApi.class)) : Optional.empty(); - } - - public Optional getHomeCoachApi() { - return apiCreator != null ? Optional.of(apiCreator.getAPI(HealthyhomecoachApi.class)) : Optional.empty(); - } - - public Optional getThermostatApi() { - return apiCreator != null ? Optional.of(apiCreator.getAPI(ThermostatApi.class)) : Optional.empty(); - } - - public Optional 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 getStationsDataBody(@Nullable String equipmentId) { - Optional data = getStationApi().map(api -> api.getstationsdata(equipmentId, true).getBody()); - updateStatus(ThingStatus.ONLINE); - return data; - } - - public List getStationMeasureResponses(String equipmentId, @Nullable String moduleId, String scale, - List types) { - List 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 getHomecoachDataBody(@Nullable String equipmentId) { - Optional data = getHomeCoachApi() - .map(api -> api.gethomecoachsdata(equipmentId).getBody()); - updateStatus(ThingStatus.ONLINE); - return data; - } - - public Optional getThermostatsDataBody(@Nullable String equipmentId) { - Optional data = getThermostatApi() - .map(api -> api.getthermostatsdata(equipmentId).getBody()); - updateStatus(ThingStatus.ONLINE); - return data; - } - - public Optional getWelcomeDataBody(@Nullable String homeId) { - Optional 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 findNAThing(@Nullable String searchedId) { - List things = getThing().getThings(); - Stream 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 modules = new HashSet<>(); - if (WELCOME_EVENTS.contains(event.getEventType()) || PRESENCE_EVENTS.contains(event.getEventType())) { - String cameraId = event.getCameraId(); - if (cameraId != null) { - Optional camera = findNAThing(cameraId); - camera.ifPresent(modules::add); - } - } - if (HOME_EVENTS.contains(event.getEventType())) { - String homeId = event.getHomeId(); - if (homeId != null) { - Optional home = findNAThing(homeId); - home.ifPresent(modules::add); - } - } - if (PERSON_EVENTS.contains(event.getEventType())) { - List persons = event.getPersons(); - persons.forEach(person -> { - String personId = person.getId(); - if (personId != null) { - Optional 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 index ac76168c6c2b4..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDataListener.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 index 2992e4ece7270..73729156fa11a 100644 --- 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 @@ -12,246 +12,325 @@ */ package org.openhab.binding.netatmo.internal.handler; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.VENDOR; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.math.BigDecimal; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.Callable; 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.binding.netatmo.internal.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.ConnectionListener; +import org.openhab.binding.netatmo.internal.api.ConnectionStatus; +import org.openhab.binding.netatmo.internal.api.ModuleType; +import org.openhab.binding.netatmo.internal.api.ModuleType.RefreshPolicy; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureLimit; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.WeatherApi; +import org.openhab.binding.netatmo.internal.api.dto.NADevice; +import org.openhab.binding.netatmo.internal.api.dto.NAEvent; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.MeasuresChannelHelper; +import org.openhab.binding.netatmo.internal.config.MeasureChannelConfig; +import org.openhab.binding.netatmo.internal.config.NetatmoThingConfiguration; +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.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; -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 + * {@link NetatmoDeviceHandler} is the abstract class that handles + * common behaviors of all netatmo bridges (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 abstract class NetatmoDeviceHandler extends AbstractNetatmoThingHandler { +public class NetatmoDeviceHandler extends BaseBridgeHandler implements ConnectionListener { + private final Logger logger = LoggerFactory.getLogger(NetatmoDeviceHandler.class); - private static final int MIN_REFRESH_INTERVAL = 2000; - private static final int DEFAULT_REFRESH_INTERVAL = 300000; + private final Map dataListeners = new ConcurrentHashMap<>(); + + private final List channelHelpers; + protected final NetatmoDescriptionProvider descriptionProvider; + private final Optional measureChannelHelper; + protected final ApiBridge apiBridge; + + protected @Nullable NAThing naThing; + protected @NonNullByDefault({}) NetatmoThingConfiguration config; - 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 childs = new ConcurrentHashMap<>(); - public NetatmoDeviceHandler(Thing thing, final TimeZoneProvider timeZoneProvider) { - super(thing, timeZoneProvider); + public NetatmoDeviceHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge); + this.apiBridge = apiBridge; + this.descriptionProvider = descriptionProvider; + this.channelHelpers = channelHelpers; + + measureChannelHelper = channelHelpers.stream().filter(c -> c instanceof MeasuresChannelHelper).findFirst() + .map(MeasuresChannelHelper.class::cast); } @Override - protected void initializeThing() { - defineRefreshInterval(); - updateStatus(ThingStatus.ONLINE); - scheduleRefreshJob(); - } + public void initialize() { + logger.debug("initializing handler for thing {}", getThing().getUID()); - private void scheduleRefreshJob() { - RefreshStrategy strategy = refreshStrategy; - if (strategy == null) { - return; + config = getThing().getConfiguration().as(NetatmoThingConfiguration.class); + ModuleType supportedThingType = ModuleType.valueOf(getThing().getThingTypeUID().getId()); + NetatmoDeviceHandler bridgeHandler = getBridgeHandler(getBridge()); + if (bridgeHandler != null) { + bridgeHandler.registerDataListener(config.id, this); } - 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; - } + + refreshStrategy = supportedThingType.getRefreshPeriod() == RefreshPolicy.AUTO ? new RefreshStrategy(-1) + : supportedThingType.getRefreshPeriod() == RefreshPolicy.CONFIG + ? new RefreshStrategy(config.refreshInterval) + : null; + + measureChannelHelper.ifPresent(channelHelper -> channelHelper.collectMeasuredChannels()); + apiBridge.addConnectionListener(this); + } + + @Override + public void notifyStatusChange(ConnectionStatus connectionStatus) { + if (connectionStatus.isConnected()) { + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, connectionStatus.getMessage()); scheduleRefreshJob(); - }, delay, TimeUnit.SECONDS); + } else { + freeRefreshJob(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, connectionStatus.getMessage()); + } } @Override public void dispose() { logger.debug("Running dispose()"); + freeRefreshJob(); + apiBridge.removeConnectionListener(this); + NetatmoDeviceHandler bridgeHandler = getBridgeHandler(getBridge()); + if (bridgeHandler != null) { + bridgeHandler.unregisterDataListener(this); + } + } + + private void freeRefreshJob() { ScheduledFuture job = refreshJob; if (job != null) { job.cancel(true); - refreshJob = null; } + refreshJob = null; } - protected abstract Optional updateReadings(); + private void scheduleRefreshJob() { + RefreshStrategy strategy = refreshStrategy; + if (strategy != null) { + long delay = strategy.nextRunDelayInS(); + logger.debug("Scheduling update channel thread in {} s", delay); - protected void updateProperties(DEVICE deviceData) { + updateChannels(false); + ScheduledFuture job = refreshJob; + if (job != null) { + job.cancel(false); + } + refreshJob = scheduler.schedule(() -> scheduleRefreshJob(), delay, TimeUnit.SECONDS); + } } - @Override - protected void updateChannels() { - updateChannels(true); + protected NAThing updateReadings() throws NetatmoException { + throw new NetatmoException("Should not be called"); } - 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 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()); + private synchronized void updateChannels(boolean requireDefinedRefreshInterval) { + 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 {}", config.id); + try { + NAThing newDeviceReading = updateReadings(); + logger.debug("Successfully updated device {} readings! Now updating channels", config.id); + setNAThing(newDeviceReading); + updateProperties(newDeviceReading); + newDeviceReading.getLastSeen().ifPresent(strategy::setDataTimeStamp); + } catch (NetatmoException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Unable to connect Netatmo API : " + e.getLocalizedMessage()); } - super.updateChannels(); - updateChildModules(); + } else { + logger.debug("Data still valid for device {}", config.id); + } + updateChildModules(); + } + } + + protected void updateChildModules() { + NAThing localNaThing = this.naThing; + if (localNaThing != null) { + if (localNaThing instanceof NADevice) { + NADevice localNaDevice = (NADevice) localNaThing; + localNaDevice.getModules().entrySet() + .forEach(entry -> notifyListener(entry.getKey(), entry.getValue())); + } + } + } + + protected void notifyListener(String id, NAObject newData) { + NetatmoDeviceHandler listener = dataListeners.get(id); + if (listener != null) { + if (newData instanceof NAEvent) { + listener.setEvent((NAEvent) newData); + } else { + listener.setNAThing((NAThing) newData); } } } @Override - protected State getNAThingProperty(String channelId) { - try { - Optional 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; + public void handleCommand(ChannelUID channelUID, Command command) { + if (command == RefreshType.REFRESH) { + logger.debug("Refreshing '{}'", channelUID); + updateState(channelUID, getNAThingProperty(channelUID)); + } + } + + public void setNAThing(NAThing naThing) { + if (naThing.isReachable()) { + updateStatus(ThingStatus.ONLINE); + this.naThing = naThing; + channelHelpers.forEach(helper -> helper.setNewData(naThing)); + measureChannelHelper.ifPresent(channelHelper -> { + channelHelper.collectMeasuredChannels(); + if (refreshStrategy == null) { + NetatmoDeviceHandler bridgeHandler = getBridgeHandler(getBridge()); + if (bridgeHandler != null) { + bridgeHandler.callGetMeasurements(config.id, channelHelper.getMeasures()); } - 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 { + callGetMeasurements(null, channelHelper.getMeasures()); + } + }); + getThing().getChannels().stream() + .filter(channel -> !ChannelKind.TRIGGER.equals(channel.getKind()) && isLinked(channel.getUID())) + .map(channel -> channel.getUID()).forEach(channelUID -> { + updateState(channelUID, getNAThingProperty(channelUID)); + }); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device is not connected"); + } + } + + private void callGetMeasurements(@Nullable String moduleId, Map measures) { + measures.keySet().forEach(measureDef -> { + double result = Double.NaN; + try { + WeatherApi api = apiBridge.getRestManager(WeatherApi.class); + if (api != null) { + if (measureDef.limit == MeasureLimit.NONE) { + result = api.getMeasurements(config.id, moduleId, measureDef.period, measureDef.type); } else { - return UnDefType.UNDEF; + result = api.getMeasurements(config.id, moduleId, measureDef.period, measureDef.type, + measureDef.limit); } + } + } catch (NetatmoException e) { + logger.warn("Error getting measurement {} on period {} for module {} : {}", measureDef.type, + measureDef.period, moduleId, e.getMessage()); + } + measures.put(measureDef, result); + }); + } + + private void updateProperties(NAThing naThing) { + int firmware = naThing.getFirmware(); + if (firmware != -1) { + Map properties = editProperties(); + ModuleType modelId = naThing.getType(); + properties.put(Thing.PROPERTY_VENDOR, VENDOR); + properties.put(Thing.PROPERTY_FIRMWARE_VERSION, Integer.toString(firmware)); + properties.put(Thing.PROPERTY_MODEL_ID, modelId.name()); + updateProperties(properties); + } + } + + public void expireData() { + scheduler.schedule(() -> { + RefreshStrategy strategy = refreshStrategy; + if (strategy != null) { + strategy.expireData(); } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - logger.debug("The device has no method to access {} property ", channelId); - return UnDefType.NULL; + scheduleRefreshJob(); + + }, 3, TimeUnit.SECONDS); + } + + protected void tryApiCall(Callable function) { + try { + function.call(); + expireData(); + } catch (Exception e) { + logger.warn("Error calling api : {}", e.getMessage()); } + } - return super.getNAThingProperty(channelId); + private void registerDataListener(String id, NetatmoDeviceHandler dataListener) { + dataListeners.put(id, dataListener); + updateChildModules(); } - 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); - }); + private void unregisterDataListener(NetatmoDeviceHandler dataListener) { + dataListeners.entrySet().forEach(entry -> { + if (entry.getValue().equals(dataListener)) { + dataListeners.remove(entry.getKey()); + } }); } - /* - * 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()); + public void setEvent(NAEvent event) { } - protected abstract Optional getDataTimestamp(); + protected void updateIfLinked(String group, String channelName, State state) { + ChannelUID channelUID = new ChannelUID(thing.getUID(), group, channelName); + if (isLinked(channelUID)) { + updateState(channelUID, state); + } + } - public void expireData() { - RefreshStrategy strategy = refreshStrategy; - if (strategy != null) { - strategy.expireData(); + private State getNAThingProperty(ChannelUID channelUID) { + for (AbstractChannelHelper helper : channelHelpers) { + State state = helper.getNAThingProperty(channelUID); + if (state != null) { + return state; + } } + return UnDefType.UNDEF; } - protected Optional getDevice() { - return Optional.ofNullable(device); + protected @Nullable NetatmoDeviceHandler getBridgeHandler(@Nullable Bridge bridge) { + if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) { + return (NetatmoDeviceHandler) bridge.getHandler(); + } + return null; } } 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 index 4c2e8f21cf8b5..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoModuleHandler.java +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 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 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 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/PersonHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PersonHandler.java new file mode 100644 index 0000000000000..c4ea148b37d6d --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PersonHandler.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2010-2021 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.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +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.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.EventType; +import org.openhab.binding.netatmo.internal.api.ModuleType; +import org.openhab.binding.netatmo.internal.api.dto.NAEvent; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeEvent; +import org.openhab.binding.netatmo.internal.api.dto.NASnapshot; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link PersonHandler} is the class used to handle the Welcome Home Data + * + * @author Ing. Peter Weiss - Initial contribution + * + */ +@NonNullByDefault +public class PersonHandler extends NetatmoDeviceHandler { + private final Logger logger = LoggerFactory.getLogger(PersonHandler.class); + private ZonedDateTime maxEventTime; + + public PersonHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + String lastEvent = editProperties().get(PROPERTY_MAX_EVENT_TIME); + maxEventTime = lastEvent != null ? ZonedDateTime.parse(lastEvent) : Instant.EPOCH.atZone(ZoneOffset.UTC); + } + + private @Nullable HomeSecurityHandler getHomeHandler() { + NetatmoDeviceHandler handler = super.getBridgeHandler(getBridge()); + return handler != null ? (HomeSecurityHandler) handler : null; + } + + @Override + public void initialize() { + super.initialize(); + HomeSecurityHandler homeHandler = getHomeHandler(); + if (homeHandler != null) { + List lastEvents = homeHandler.getLastEventOf(config.id); + if (lastEvents.size() > 0) { + setEvent(lastEvents.get(0)); + } + } + } + + @Override + public void setNAThing(NAThing naModule) { + super.setNAThing(naModule); + HomeSecurityHandler homeHandler = getHomeHandler(); + if (homeHandler != null) { + descriptionProvider.setStateOptions( + new ChannelUID(getThing().getUID(), GROUP_PERSON_EVENT, CHANNEL_EVENT_CAMERA_ID), + homeHandler.getCameras().values().stream().map(p -> new StateOption(p.getId(), p.getName())) + .collect(Collectors.toList())); + } + } + + @Override + public void setEvent(NAEvent event) { + if (event.getTime().isAfter(maxEventTime)) { + logger.debug("Updating person with event : {}", event.toString()); + + maxEventTime = event.getTime(); + updateProperty(PROPERTY_MAX_EVENT_TIME, maxEventTime.toString()); + + updateIfLinked(GROUP_PERSON_EVENT, CHANNEL_EVENT_TIME, new DateTimeType(event.getTime())); + updateIfLinked(GROUP_PERSON_EVENT, CHANNEL_EVENT_CAMERA_ID, toStringType(event.getCameraId())); + updateIfLinked(GROUP_WELCOME_EVENT, CHANNEL_EVENT_SUBTYPE, + event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL)); + + NASnapshot snapshot = event.getSnapshot(); + if (snapshot != null) { + String url = snapshot.getUrl(); + updateIfLinked(GROUP_PERSON_EVENT, CHANNEL_EVENT_SNAPSHOT, toRawType(url)); + updateIfLinked(GROUP_PERSON_EVENT, CHANNEL_EVENT_SNAPSHOT_URL, toStringType(url)); + } + + EventType eventType = event.getEventType(); + if (eventType.appliesOn(ModuleType.NAPerson)) { + updateIfLinked(GROUP_PERSON, CHANNEL_PERSON_AT_HOME, OnOffType.from(eventType == EventType.PERSON)); + triggerChannel(CHANNEL_HOME_EVENT, eventType.name()); + } + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if ((command instanceof OnOffType) && CHANNEL_PERSON_AT_HOME.equals(channelUID.getIdWithoutGroup())) { + HomeSecurityHandler homeHandler = getHomeHandler(); + if (homeHandler != null) { + homeHandler.callSetPersonAway(config.id, command == OnOffType.OFF); + } + } else { + super.handleCommand(channelUID, command); + } + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PlugHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PlugHandler.java new file mode 100644 index 0000000000000..57d2555123cbe --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PlugHandler.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2021 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.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NAPlug; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingStatus; + +/** + * {@link PlugHandler} is the class used to handle the plug + * device of a thermostat set + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PlugHandler extends NetatmoDeviceHandler { + + public PlugHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + } + + public @NonNullByDefault({}) HomeEnergyHandler getHomeHandler() { + Bridge bridge = getBridge(); + if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) { + return (HomeEnergyHandler) bridge.getHandler(); + } + return null; + } + + @Override + protected NAPlug updateReadings() throws NetatmoException { + HomeEnergyHandler handler = getHomeHandler(); + if (handler != null) { + NAHome localHome = handler.getHome(); + if (localHome != null) { + return (NAPlug) Objects.requireNonNullElse(localHome.getModule(config.id), new NAPlug()); + } + } + return new NAPlug(); + } + + // public int getSetpointDefaultDuration() { + // HomeEnergyHandler bridgeHandler = getHomeHandler(); + // return bridgeHandler != null ? bridgeHandler.getSetpointDefaultDuration() : 120; + // } + // + // public void callSetThermMode(String moduleId, SetpointMode targetMode) { + // EnergyApi api = apiBridge.getRestManager(EnergyApi.class); + // tryApiCall(() -> api != null + // ? api.setthermpoint(config.id, moduleId, targetMode, + // targetMode == SetpointMode.MAX ? getSetpointEndTimeFromNow(getSetpointDefaultDuration()) : 0, 0) + // : false); + // } + // + // public void callSetThermTemp(String moduleId, double temperature) { + // EnergyApi api = apiBridge.getRestManager(EnergyApi.class); + // tryApiCall(() -> api != null ? api.setthermpoint(config.id, moduleId, SetpointMode.MANUAL, + // getSetpointEndTimeFromNow(getSetpointDefaultDuration()), temperature) : false); + // } + // + // public void callSwitchSchedule(String moduleId, String schedule) { + // EnergyApi api = apiBridge.getRestManager(EnergyApi.class); + // tryApiCall(() -> api != null ? api.switchschedule(config.id, moduleId, schedule) : false); + // } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PresenceHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PresenceHandler.java new file mode 100644 index 0000000000000..fab25e19c561a --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/PresenceHandler.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2021 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.util.List; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.PresenceLightMode; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.binding.netatmo.internal.channelhelper.PresenceChannelHelper; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.Command; + +/** + * {@link PresenceHandler} is the class used to handle Presence camera data + * + * @author Sven Strohschein - Initial contribution + */ +@NonNullByDefault +public class PresenceHandler extends CameraHandler { + private final Optional presenceHelper; + + public PresenceHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + presenceHelper = channelHelpers.stream().filter(c -> c instanceof PresenceChannelHelper).findFirst() + .map(PresenceChannelHelper.class::cast); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + String channelId = channelUID.getId(); + switch (channelId) { + case CHANNEL_CAMERA_FLOODLIGHT: + switchFloodlight(command == OnOffType.ON); + break; + case CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE: + switchFloodlightAutoMode(command == OnOffType.ON); + break; + default: + super.handleCommand(channelUID, command); + } + } + + private void switchFloodlight(boolean isOn) { + if (isOn) { + changeFloodlightMode(PresenceLightMode.ON); + } else { + switchFloodlightAutoMode(presenceHelper.get().getAutoMode() == OnOffType.ON); + } + } + + private void switchFloodlightAutoMode(boolean isAutoMode) { + presenceHelper.get().setAutoMode(OnOffType.from(isAutoMode)); + changeFloodlightMode(isAutoMode ? PresenceLightMode.AUTO : PresenceLightMode.OFF); + } + + private void changeFloodlightMode(PresenceLightMode mode) { + String localCameraURL = getLocalCameraURL(); + if (localCameraURL != null) { + tryApiCall(() -> apiBridge.getHomeApi().changeFloodLightMode(localCameraURL, mode)); + } + } +} 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/handler/RefreshStrategy.java similarity index 70% rename from bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/RefreshStrategy.java rename to bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/RefreshStrategy.java index 3cb2c30b35836..254f0ed6b5046 100644 --- 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/handler/RefreshStrategy.java @@ -10,12 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.netatmo.internal; +package org.openhab.binding.netatmo.internal.handler; -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; @@ -31,16 +29,15 @@ */ @NonNullByDefault public class RefreshStrategy { + private static final int DEFAULT_DELAY_SEC = 30; + private static final int SEARCH_REFRESH_INTERVAL_SEC = 120; - private Logger logger = LoggerFactory.getLogger(RefreshStrategy.class); + private final 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 long dataValidityPeriod; + private @Nullable ZonedDateTime dataTimeStamp; private boolean searchRefreshInterval; - @Nullable - private Integer dataTimestamp0; + private @Nullable ZonedDateTime 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 @@ -57,26 +54,24 @@ public RefreshStrategy(int dataValidityPeriod) { expireData(); } - @SuppressWarnings("null") - public void setDataTimeStamp(Integer dataTimestamp, ZoneId zoneId) { + public void setDataTimeStamp(ZonedDateTime dataTimestamp) { 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; + } else if (dataTimestamp.isAfter(dataTimestamp0)) { + dataValidityPeriod = ChronoUnit.MILLIS.between(dataTimestamp0, dataTimestamp); 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(); + this.dataTimeStamp = dataTimestamp; } public long dataAge() { - long now = Calendar.getInstance().getTimeInMillis(); - return now - dataTimeStamp; + return ChronoUnit.MILLIS.between(dataTimeStamp, ZonedDateTime.now()); } public boolean isDataOutdated() { @@ -84,13 +79,13 @@ public boolean isDataOutdated() { } public long nextRunDelayInS() { - return searchRefreshInterval ? SEARCH_REFRESH_INTERVAL - : Math.max(0, (dataValidityPeriod - dataAge())) / 1000 + DEFAULT_DELAY; + return searchRefreshInterval ? SEARCH_REFRESH_INTERVAL_SEC + : Math.max(0, (dataValidityPeriod - dataAge())) / 1000 + DEFAULT_DELAY_SEC; } public void expireData() { ZonedDateTime now = ZonedDateTime.now().minus(this.dataValidityPeriod, ChronoUnit.MILLIS); - dataTimeStamp = now.toInstant().toEpochMilli(); + dataTimeStamp = now; } public boolean isSearchingRefreshInterval() { diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/RoomHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/RoomHandler.java new file mode 100644 index 0000000000000..23b4bb24ab4a3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/RoomHandler.java @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2010-2021 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.binding.netatmo.internal.utils.ChannelTypeUtils.commandToQuantity; +import static org.openhab.binding.netatmo.internal.utils.NetatmoCalendarUtils.getSetpointEndTimeFromNow; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.action.RoomActions; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.dto.NAHome; +import org.openhab.binding.netatmo.internal.api.dto.NARoom; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link RoomHandler} is the class used to handle the valve + * module of a thermostat set + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class RoomHandler extends NetatmoDeviceHandler { + + private final Logger logger = LoggerFactory.getLogger(RoomHandler.class); + + public RoomHandler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + } + + private @NonNullByDefault({}) HomeEnergyHandler getHomeHandler() { + Bridge bridge = getBridge(); + if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) { + return (HomeEnergyHandler) bridge.getHandler(); + } + return null; + } + + @Override + protected NARoom updateReadings() throws NetatmoException { + HomeEnergyHandler handler = getHomeHandler(); + if (handler != null) { + NAHome localHome = handler.getHome(); + if (localHome != null) { + return Objects.requireNonNullElse(localHome.getRoom(config.id), new NARoom()); + } + } + return new NARoom(); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + super.handleCommand(channelUID, command); + } else { + NARoom currentData = (NARoom) naThing; + HomeEnergyHandler handler = getHomeHandler(); + if (handler != null) { + NAHome home = handler.getHome(); + if (currentData != null && home != null) { + String channelName = channelUID.getIdWithoutGroup(); + String groupName = channelUID.getGroupId(); + if (channelName.equals(CHANNEL_SETPOINT_MODE)) { + SetpointMode targetMode = SetpointMode.valueOf(command.toString()); + if (targetMode == SetpointMode.MANUAL) { + // updateState(channelUID, toStringType(currentData.getSetpointMode())); + logger.info("Switch to 'Manual' is done by setting a setpoint temp, command ignored"); + } else { + callSetRoomThermMode(home.getId(), config.id, targetMode); + } + } else if (GROUP_TH_SETPOINT.equals(groupName) && channelName.equals(CHANNEL_VALUE)) { + QuantityType quantity = commandToQuantity(command, MeasureClass.INTERIOR_TEMPERATURE); + if (quantity != null) { + callSetRoomThermTemp(home.getId(), config.id, quantity.doubleValue()); + updateState(channelUID, quantity); + handler.expireData(); + } else { + logger.warn("Incorrect command '{}' on channel '{}'", command, channelName); + } + } + } + } + } + } + + public int getSetpointDefaultDuration() { + HomeEnergyHandler bridgeHandler = getHomeHandler(); + return bridgeHandler != null ? bridgeHandler.getSetpointDefaultDuration() : 120; + } + + private void callSetRoomThermMode(String homeId, String roomId, SetpointMode targetMode) { + apiBridge.getEnergyApi().ifPresent(api -> { + tryApiCall(() -> api.setRoomThermpoint(homeId, roomId, targetMode, + targetMode == SetpointMode.MAX ? getSetpointEndTimeFromNow(getSetpointDefaultDuration()) : 0, 0)); + }); + } + + private void callSetRoomThermTemp(String homeId, String roomId, double temperature) { + apiBridge.getEnergyApi().ifPresent(api -> { + tryApiCall(() -> api.setRoomThermpoint(homeId, roomId, SetpointMode.MANUAL, + getSetpointEndTimeFromNow(getSetpointDefaultDuration()), temperature)); + }); + } + + public void thingActionCallSetRoomThermTemp(double temperature, long endtime, SetpointMode mode) { + HomeEnergyHandler handler = getHomeHandler(); + NAHome home = handler.getHome(); + if (home != null) { + apiBridge.getEnergyApi().ifPresent(api -> { + tryApiCall(() -> api.setRoomThermpoint(home.getId(), config.id, mode, endtime, temperature)); + }); + } else { + logger.info("No home available to launch setRoomThermpoint action"); + } + } + + @Override + public Collection> getServices() { + return Collections.singletonList(RoomActions.class); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/Therm1Handler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/Therm1Handler.java new file mode 100644 index 0000000000000..34d64c39dcb05 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/Therm1Handler.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2010-2021 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.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.NetatmoDescriptionProvider; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.channelhelper.AbstractChannelHelper; +import org.openhab.core.thing.Bridge; + +/** + * {@link Therm1Handler} is the class used to handle the thermostat + * module of a thermostat set + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class Therm1Handler extends NetatmoDeviceHandler { + + // private final Logger logger = LoggerFactory.getLogger(Therm1Handler.class); + + public Therm1Handler(Bridge bridge, List channelHelpers, ApiBridge apiBridge, + NetatmoDescriptionProvider descriptionProvider) { + super(bridge, channelHelpers, apiBridge, descriptionProvider); + } + + // TODO : if it happens that Therm1Handler does not any longer have code, this class could be removed + + // private @Nullable PlugHandler getPlugHandler() { + // NetatmoDeviceHandler handler = super.getBridgeHandler(getBridge()); + // return handler != null ? (PlugHandler) handler : null; + // } + + // @Override + // public void handleCommand(ChannelUID channelUID, Command command) { + // if (command instanceof RefreshType) { + // super.handleCommand(channelUID, command); + // } else { + // NAThermostat currentData = (NAThermostat) naThing; + // PlugHandler handler = getPlugHandler(); + // if (currentData != null && handler != null) { + // String channelName = channelUID.getIdWithoutGroup(); + // String groupName = channelUID.getGroupId(); + // if (channelName.equals(CHANNEL_SETPOINT_MODE)) { + // SetpointMode targetMode = SetpointMode.valueOf(command.toString()); + // if (targetMode == SetpointMode.MANUAL) { + // updateState(channelUID, toStringType(currentData.getSetpointMode())); + // logger.info("Switch to 'Manual' is done by setting a setpoint temp, command ignored"); + // } else { + // handler.callSetThermMode(config.id, targetMode); + // } + // } else if (GROUP_TH_SETPOINT.equals(groupName) && channelName.equals(CHANNEL_VALUE)) { + // QuantityType quantity = commandToQuantity(command, MeasureClass.INTERIOR_TEMPERATURE); + // if (quantity != null) { + // handler.callSetThermTemp(config.id, quantity.doubleValue()); + // } else { + // logger.warn("Incorrect command '{}' on channel '{}'", command, channelName); + // } + // } + // } + // } + // } +} 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 index d2da3ea41adce..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/homecoach/NAHealthyHomeCoachHandler.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - - public NAHealthyHomeCoachHandler(Thing thing, final TimeZoneProvider timeZoneProvider) { - super(thing, timeZoneProvider); - } - - @Override - protected Optional 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 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 index aca402699ad7c..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/presence/NAPresenceCameraHandler.java +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 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/BaseDsI18n.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/BaseDsI18n.java new file mode 100644 index 0000000000000..6d79973d3f032 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/BaseDsI18n.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2010-2021 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.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.TranslationProvider; +import org.osgi.framework.Bundle; +import org.osgi.service.component.ComponentContext; + +/** + * The {@link BaseDsI18n} provides the internationalization service in form of the + * {@link org.openhab.core.i18n.TranslationProvider} of the Netatmo -Bindings. + * So this class can be implement e.g. by provider implementations like the + * {@link org.openhab.core.thing.type.ChannelTypeProvider}. + * + * @author Michael Ochel - initial contributer + * @author Matthias Siegele - initial contributer + * @author Gaël L'hopital - adapted to Netatmo Binding + */ +@NonNullByDefault +public abstract class BaseDsI18n { + + private static final String LABEL_ID = "label"; + private static final String DESC_ID = "description"; + private static final String SEPERATOR = "."; + + private final TranslationProvider translationProvider; + private @NonNullByDefault({}) Bundle bundle; + + public BaseDsI18n(TranslationProvider translationProvider) { + this.translationProvider = translationProvider; + } + + /** + * Initializes the {@link BaseDsI18n}. + * + * @param componentContext + */ + protected void activate(ComponentContext componentContext) { + this.bundle = componentContext.getBundleContext().getBundle(); + } + + /** + * Disposes the {@link BaseDsI18n}. + * + * @param componentContext + */ + protected void deactivate(ComponentContext componentContext) { + this.bundle = null; + } + + /** + * Returns the internationalized text in the language of the {@link Locale} of the given key. If the key an does not + * exist at the internationalization of the {@link Locale} the {@link Locale#ENGLISH} will be used. If the key dose + * not exists in {@link Locale#ENGLISH}, too, the key will be returned. + * + * @param key + * @param locale + * @return internationalized text + */ + private String getText(String key, @Nullable Locale locale) { + String result = translationProvider.getText(bundle, key, + translationProvider.getText(bundle, key, key, Locale.ENGLISH), locale); + return result != null ? result : key; + } + + /** + * Returns the internationalized label in the language of the {@link Locale} of the given key. + * + * @param key of internationalization label + * @param locale of the wished language + * @return internationalized label + * @see #getText(String, Locale) + */ + protected String getLabelText(String key, @Nullable Locale locale) { + return getText(buildIdentifier(key, LABEL_ID), locale); + } + + /** + * Returns the internationalized description in the language of the {@link Locale} of the given key. + * + * @param key of internationalization description + * @param locale of the wished language + * @return internationalized description + * @see #getText(String, Locale) + */ + protected String getDescText(String key, @Nullable Locale locale) { + return getText(buildIdentifier(key, DESC_ID), locale); + } + + /** + * Builds the key {@link String} through the given {@link Object}s.
+ * The key will be build as lower case {@link Object#toString()} + {@link #SEPERATOR} + {@link Object#toString()} + + * ... , so the result {@link String} will be look like "object1_object2" + * + * @param parts to join + * @return key + */ + private static String buildIdentifier(String p1, String p2) { + return String.join(SEPERATOR, "thing-type", BINDING_ID, p1, p2); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoDeviceThingTypeProvider.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoDeviceThingTypeProvider.java new file mode 100644 index 0000000000000..78aba856a62af --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoDeviceThingTypeProvider.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2010-2021 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.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.ModuleType; +import org.openhab.binding.netatmo.internal.api.ModuleType.RefreshPolicy; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants; +import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingTypeProvider; +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.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; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@Component(service = ThingTypeProvider.class) +@NonNullByDefault +public class NetatmoDeviceThingTypeProvider extends BaseDsI18n implements ThingTypeProvider { + private final Logger logger = LoggerFactory.getLogger(NetatmoDeviceThingTypeProvider.class); + + @Activate + public NetatmoDeviceThingTypeProvider(@Reference TranslationProvider translationProvider) { + super(translationProvider); + } + + @Override + public Collection getThingTypes(@Nullable Locale locale) { + List thingTypes = new LinkedList<>(); + for (ModuleType supportedThingType : ModuleType.values()) { + ThingType thingType = getThingType(supportedThingType.getThingTypeUID(), locale); + if (thingType != null) { + thingTypes.add(thingType); + } + } + return thingTypes; + } + + @Override + public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) { + if (BINDING_ID.equalsIgnoreCase(thingTypeUID.getBindingId())) { + try { + ModuleType supportedThingType = ModuleType.valueOf(thingTypeUID.getId()); + String configDescription = BINDING_ID + ":" + + (supportedThingType.getSignalLevels() == NetatmoConstants.NO_RADIO ? "virtual" + : supportedThingType.getRefreshPeriod() == RefreshPolicy.CONFIG ? "configurable" + : "device"); + + ThingTypeBuilder thingTypeBuilder = ThingTypeBuilder + .instance(thingTypeUID, getLabelText(thingTypeUID.getId(), locale)) + .withDescription(getDescText(thingTypeUID.getId(), locale)) + .withProperties(getProperties(supportedThingType)).withRepresentationProperty(EQUIPMENT_ID) + .withChannelGroupDefinitions(getGroupDefinitions(supportedThingType)) + .withConfigDescriptionURI(new URI(configDescription)); + + List extensions = supportedThingType.getExtensions(); + if (extensions.size() > 0) { + thingTypeBuilder.withExtensibleChannelTypeIds(extensions); + } + + ThingTypeUID thingType = supportedThingType.getBridgeThingType(); + if (thingType != null) { + thingTypeBuilder.withSupportedBridgeTypeUIDs(Arrays.asList(thingType.getAsString())); + } + + return thingTypeBuilder.buildBridge(); + } catch (IllegalArgumentException | URISyntaxException e) { + logger.warn("Unable to define ModuleType for thingType {} : {}", thingTypeUID.getId(), e.getMessage()); + } + } + return null; + } + + private List getGroupDefinitions(ModuleType supportedThingType) { + List groupDefinitions = new ArrayList<>(); + for (String group : supportedThingType.getGroups()) { + ChannelGroupTypeUID groupType = new ChannelGroupTypeUID(BINDING_ID, group); + groupDefinitions.add(new ChannelGroupDefinition(group, groupType)); + } + return groupDefinitions; + } + + private Map getProperties(ModuleType supportedThingType) { + Map properties = new HashMap<>(); + + if (supportedThingType.getSignalLevels() != NetatmoConstants.NO_RADIO) { + properties.put(Thing.PROPERTY_VENDOR, VENDOR); + properties.put(Thing.PROPERTY_MODEL_ID, supportedThingType.name()); + } + + return properties; + } +} 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 index df2b0cf98df0f..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAMainHandler.java +++ /dev/null @@ -1,297 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - private Map channelMeasurements = new ConcurrentHashMap<>(); - - public NAMainHandler(Thing thing, final TimeZoneProvider timeZoneProvider) { - super(thing, timeZoneProvider); - } - - @Override - protected Optional updateReadings() { - Optional 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 channels = new ArrayList<>(); - List 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 channels = new ArrayList<>(); - List 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 channels = new ArrayList<>(); - List 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 getDataTimestamp() { - return getDevice().map(d -> d.getLastStatusStore()); - } - - @Override - protected boolean isReachable() { - boolean result = false; - Optional 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 index dbd271a3986ae..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule1Handler.java +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - private Map 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 channels = new ArrayList<>(); - List 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 channels = new ArrayList<>(); - List 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 channels = new ArrayList<>(); - List 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 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 index e140599e5aa05..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule2Handler.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - - 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 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 index efe8ed32f238a..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule3Handler.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - private Map 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 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 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 index 2d1cc4c95dc22..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAModule4Handler.java +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - private Map 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 channels = new ArrayList<>(); - List 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 channels = new ArrayList<>(); - List 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 channels = new ArrayList<>(); - List 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 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 index 616e47a614423..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/thermostat/NAPlugHandler.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - - public NAPlugHandler(Thing thing, final TimeZoneProvider timeZoneProvider) { - super(thing, timeZoneProvider); - } - - @Override - protected Optional updateReadings() { - Optional 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 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 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 index 01987073de047..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/thermostat/NATherm1Handler.java +++ /dev/null @@ -1,320 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - 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 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 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 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 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 thermProgramList) { - NATimeTableItem lastProgram = null; - Calendar now = Calendar.getInstance(); - long diff = (now.getTimeInMillis() - getNetatmoProgramBaseTime()) / 1000 / 60; - - Optional currentProgram = thermProgramList.stream() - .filter(p -> p.isSelected() != null && p.isSelected()).findFirst(); - - if (currentProgram.isPresent()) { - Stream pastPrograms = nonNullStream(currentProgram.get().getTimetable()) - .filter(t -> t.getMOffset() < diff); - Optional program = pastPrograms.reduce((first, second) -> second); - if (program.isPresent()) { - lastProgram = program.get(); - } - } - - return lastProgram; - } - - private int getNextProgramTime(List 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 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 quantity = ((QuantityType) 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 getApi() { - return getBridgeHandler().flatMap(handler -> handler.getThermostatApi()); - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/BindingUtils.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/BindingUtils.java new file mode 100644 index 0000000000000..da9e425754921 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/BindingUtils.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2021 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.util.Collections; +import java.util.Dictionary; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.osgi.service.component.ComponentContext; + +/** + * + * @author Gaël L'hopital - Initial contribution + * + */ + +public class BindingUtils { + public static Map ComponentContextToMap(ComponentContext componentContext) { + Dictionary properties = componentContext.getProperties(); + List keys = Collections.list(properties.keys()); + Map dictCopy = keys.stream().collect(Collectors.toMap(Function.identity(), properties::get)); + return dictCopy; + } +} 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 index 0000000000000..6b8445e03c6d0 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/ChannelTypeUtils.java @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2010-2021 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.Instant; +import java.time.ZoneId; +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.NetatmoConstants; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.Measure; +import org.openhab.binding.netatmo.internal.api.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 + * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules + * + */ +@NonNullByDefault +public class ChannelTypeUtils { + + public static @Nullable QuantityType commandToQuantity(Command command, MeasureClass measureClass) { + Measure measureDef = NetatmoConstants.NA_MEASURES.get(measureClass); + if (measureDef != null) { + 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(@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); + } + + private static ZonedDateTime toZonedDateTime(long netatmoTS, ZoneId zoneId) { + Instant i = Instant.ofEpochSecond(netatmoTS); + return ZonedDateTime.ofInstant(i, zoneId); + } + + public static State toDateTimeType(@Nullable Double netatmoTS, ZoneId zoneId) { + return netatmoTS == null ? UnDefType.NULL : toDateTimeType(toZonedDateTime(netatmoTS.intValue(), zoneId)); + } + + public static State toDateTimeType(long netatmoTS, ZoneId zoneId) { + return toDateTimeType(toZonedDateTime(netatmoTS, zoneId)); + } + + public static State toDateTimeType(@Nullable ZonedDateTime zonedDateTime) { + return (zonedDateTime == null) ? UnDefType.NULL : new DateTimeType(zonedDateTime); + } + + public static State toDateTimeType(Optional lastSeen) { + return lastSeen.isPresent() ? toDateTimeType(lastSeen.get()) : UnDefType.UNDEF; + } + + // TODO Remove unused code found by UCDetector + // public static State toDecimalType(@Nullable BigDecimal value) { + // return value == null ? UnDefType.NULL : new DecimalType(value.setScale(2, RoundingMode.HALF_UP)); + // } + + // TODO Remove unused code found by UCDetector + // public static State toDecimalType(@Nullable String textualDecimal) { + // return textualDecimal == null ? UnDefType.NULL : new DecimalType(textualDecimal); + // } + + public static State toQuantityType(@Nullable Double value, @Nullable MeasureClass measureClass) { + if (value != null && !value.isNaN()) { + if (measureClass != null) { + Measure measureDef = NetatmoConstants.NA_MEASURES.get(measureClass); + if (measureDef != null) { + BigDecimal measure = new BigDecimal(Math.min(Math.max(measureDef.minValue, value), value)) + .setScale(measureDef.scale, RoundingMode.HALF_UP); + return new QuantityType<>(measure, measureDef.unit); + } + } else { + return new DecimalType(value); + } + } + return UnDefType.NULL; + } + + // TODO Remove unused code found by UCDetector + // public static State toQuantityType(@Nullable Double value, @Nullable Unit unit) { + // return value == null || value.isNaN() ? UnDefType.NULL + // : unit == null ? new DecimalType(value) + // : toQuantityType(new BigDecimal(value).setScale(2, RoundingMode.HALF_UP), unit); + // } + + 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/NetatmoCalendarUtils.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/NetatmoCalendarUtils.java new file mode 100644 index 0000000000000..29ccd3b19133f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/NetatmoCalendarUtils.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2021 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.util.Calendar; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAThermProgram; +import org.openhab.binding.netatmo.internal.api.dto.NATimeTableItem; + +/** + * This class holds various Netatmo planning related functions + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NetatmoCalendarUtils { + + public static long getSetpointEndTimeFromNow(int duration_min) { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MINUTE, duration_min); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTimeInMillis() / 1000; + } + + private static long getProgramBaseTime() { + 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() / 1000; + } + + private static long getTimeDiff() { + Calendar now = Calendar.getInstance(); + long diff = (now.getTimeInMillis() / 1000 - getProgramBaseTime()) / 60; + return diff; + } + + public static long getNextProgramTime(@Nullable NAThermProgram activeProgram) { + long diff = getTimeDiff(); + if (activeProgram != null) { + // 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 timetable = activeProgram.getTimetable(); + int next = timetable.get(0).getMOffset() + (7 * 24 * 60); + for (NATimeTableItem timeTable : timetable) { + if (timeTable.getMOffset() > diff) { + next = timeTable.getMOffset(); + break; + } + } + return next * 60 + getProgramBaseTime(); + } + return -1; + } + + public static @Nullable NATimeTableItem getCurrentProgramMode(@Nullable NAThermProgram activeProgram) { + if (activeProgram != null) { + long diff = getTimeDiff(); + return activeProgram.getTimetable().stream().filter(t -> t.getMOffset() < diff) + .reduce((first, second) -> second).orElse(null); + } + return 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/utils/WeatherUtils.java similarity index 87% rename from bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/WeatherUtils.java rename to bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/WeatherUtils.java index 0fbaf01c13baf..1290ababd5955 100644 --- 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/utils/WeatherUtils.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.netatmo.internal; +package org.openhab.binding.netatmo.internal.utils; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -73,7 +73,6 @@ public static double getDewPoint(double temperature, double humidity) { /** * Compute the Humidex index given temperature and hygrometry * - * * @param temperature in (°C) * @param hygro relative level (%) * @return Humidex index value @@ -83,4 +82,15 @@ public static double getHumidex(double temperature, double hygro) { result = temperature + 0.555555556 * (result - 10); return result; } + + /** + * 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 index 46e363f7f6a8a..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEvent.java +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 persons = new ArrayList<>(); - - public List 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 index 61c3be676db22..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEventPerson.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 index 0000000000000..6c11833dbab0b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NetatmoServlet.java @@ -0,0 +1,166 @@ +/** + * Copyright (c) 2010-2021 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.NETATMO_CALLBACK_URI; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Scanner; +import java.util.concurrent.ConcurrentHashMap; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.SecurityApi; +import org.openhab.binding.netatmo.internal.api.dto.NAWebhookEvent; +import org.openhab.binding.netatmo.internal.config.NetatmoBindingConfiguration; +import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler; +import org.openhab.binding.netatmo.internal.utils.BindingUtils; +import org.openhab.core.config.core.Configuration; +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.Deactivate; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.http.HttpService; +import org.osgi.service.http.NamespaceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * OSGi service and HTTP servlet for Netatmo Welcome Webhook. + * + * @author Gaël L'hopital - Initial contribution + */ +@Component(service = NetatmoServlet.class, configurationPid = "binding.netatmo") +@NonNullByDefault +public class NetatmoServlet extends HttpServlet { + private static final long serialVersionUID = -354583910860541214L; + private static final String CHARSET = "utf-8"; + + private final Logger logger = LoggerFactory.getLogger(NetatmoServlet.class); + private final Map dataListeners = new ConcurrentHashMap<>(); + private final HttpService httpService; + private boolean hookSet = false; + private @Nullable SecurityApi api; + private final ApiBridge apiBridge; + + @Activate + public NetatmoServlet(@Reference HttpService httpService, @Reference ApiBridge apiBridge, + ComponentContext componentContext) { + this.httpService = httpService; + this.apiBridge = apiBridge; + try { + httpService.registerServlet(NETATMO_CALLBACK_URI, this, null, httpService.createDefaultHttpContext()); + logger.debug("Started Netatmo Webhook Servlet at '{}'", NETATMO_CALLBACK_URI); + api = apiBridge.getRestManager(SecurityApi.class); + } catch (ServletException | NamespaceException e) { + logger.error("Could not start Netatmo Webhook Servlet : {}", e.getMessage()); + } + modified(BindingUtils.ComponentContextToMap(componentContext)); + } + + @Deactivate + protected void deactivate() { + httpService.unregister(NETATMO_CALLBACK_URI); + releaseWebHook(); + logger.debug("Netatmo Webhook Servlet stopped"); + } + + @Modified + protected void modified(Map config) { + NetatmoBindingConfiguration configuration = new Configuration(config).as(NetatmoBindingConfiguration.class); + SecurityApi localApi = api; + String url = configuration.webHookUrl; + if (url != null && !url.isEmpty() && localApi != null) { + String tentative = url + NETATMO_CALLBACK_URI; + try { + URI webhookURI = new URI(tentative); + logger.info("Setting Netatmo Welcome WebHook to {}", webhookURI.toString()); + hookSet = localApi.addwebhook(webhookURI); + } catch (URISyntaxException e) { + logger.warn("webhookUrl is not a valid URI '{}' : {}", tentative, e.getMessage()); + } catch (NetatmoException e) { + logger.warn("Error setting webhook : {}", e.getMessage()); + } + } + } + + private void releaseWebHook() { + SecurityApi localApi = api; + if (hookSet && localApi != null) { + logger.info("Releasing Netatmo Welcome WebHook"); + try { + localApi.dropWebhook(); + } catch (NetatmoException e) { + logger.warn("Error releasing webhook : {}", e.getMessage()); + } + } + } + + @Override + protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) + throws ServletException, IOException { + if (req != null && resp != null) { + String data = inputStreamToString(req.getInputStream()); + if (!data.isEmpty()) { + logger.debug("Event transmitted from restService : {}", data); + NAWebhookEvent event = apiBridge.deserialize(NAWebhookEvent.class, data); + NetatmoDeviceHandler targetListener = dataListeners.get(event.getHomeId()); + if (targetListener != null) { + targetListener.setEvent(event); + } + } + resp.setCharacterEncoding(CHARSET); + resp.setContentType(MediaType.APPLICATION_JSON); + resp.setHeader("Access-Control-Allow-Origin", "*"); + resp.setHeader("Access-Control-Allow-Methods", "POST"); + resp.setHeader("Access-Control-Max-Age", "3600"); + resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + resp.getWriter().write(""); + } + } + + public void registerDataListener(String id, NetatmoDeviceHandler dataListener) { + dataListeners.put(id, dataListener); + } + + public void unregisterDataListener(NetatmoDeviceHandler dataListener) { + dataListeners.entrySet().forEach(entry -> { + if (entry.getValue().equals(dataListener)) { + dataListeners.remove(entry.getKey()); + } + }); + } + + 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 index 707cdcb1224e4..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/WelcomeWebHookServlet.java +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 index cf46781802291..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeCameraHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 index 32f27cf3f027f..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandler.java +++ /dev/null @@ -1,270 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - 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 updateReadings() { - Optional 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 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 thing = findNAThing(cameraId); - if (thing.isPresent()) { - CameraHandler eventCamera = (CameraHandler) thing.get(); - Optional 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("", "").replace("", ""))) - .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 findDetectedObjectTypes(Optional eventOptional) { - Set 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 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 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 findEventMessage() { - Optional 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 findSnapshotURL() { - Optional 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 getDataTimestamp() { - Integer timestamp = dataTimeStamp; - return timestamp != null ? Optional.of(timestamp) : Optional.empty(); - } - - private State getPlaceInfo(Function infoGetFunction) { - return getDevice().map(d -> toStringType(infoGetFunction.apply(d.getPlace()))).orElse(UnDefType.UNDEF); - } - - private Optional findFirstSubEvent(NAWelcomeEvent event) { - return Optional.ofNullable(event).map(NAWelcomeEvent::getEventList) - .flatMap(subEvents -> nonNullStream(subEvents).findFirst()); - } - - private Optional 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 index 8423f206033b1..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomePersonHandler.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 { - 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 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("", "").replace("", "")) - : 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 handler = getBridgeHandler(); - Optional 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 handler = getBridgeHandler(); - Optional 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 getApi() { - return getBridgeHandler().flatMap(handler -> handler.getWelcomeApi()); - } - - private Optional getLastEvent() { - NAWelcomeEvent evt = lastEvent; - return evt != null ? Optional.of(evt) : Optional.empty(); - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/binding/binding.xml index a7c08125c6472..e68b775da2fb6 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/binding/binding.xml @@ -8,10 +8,44 @@ and Welcome Camera. - + + + Client ID provided for the application you created on http://dev.netatmo.com/createapp + + + + + Client Secret provided for the application you created + password + + + + + Your Netatmo API username (email) + + + + + Your Netatmo API password + password + + + + + Protocol, public IP and port to access OH2 server from Internet. + + + + + The reconnection interval to Netatmo API (in s). + 5400 + + + 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. + false false diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/config/config.xml index 568e00deb1bf2..73e2247ab2c1c 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/config/config.xml @@ -5,123 +5,134 @@ xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd"> - - - - Client ID provided for the application you created on http://dev.netatmo.com/createapp - - - - - Client Secret provided for the application you created - password - - - - - Your Netatmo API username (email) - - - - - Your Netatmo API password - password - - - - - Read weather station's data. - true - - - - - Read healthy home coach's data. - false - - - - - Read and Write thermostat's data. - false - - - - - Read and Access Welcome camera's data. - false - - - - - Read and Access Presence camera's data. - false + + + + Type of Measurement + + + + + SUM_BOILER_ON + + + + Observation period + + + + + + ONE_DAY + - - - Protocol, public IP and port to access OH2 server from Internet. - true + + + + Type of Measurement + + + + + SUM_RAIN + + + + Observation period + + + + + ONE_WEEK + - - - The reconnection interval to Netatmo API (in s). - 5400 - true + + + + Terminal of the Measurement + + + + + MIN + + + + Type of Measurement + + + + + + + + TEMP + + + + Observation period + + + + + + ONE_WEEK - - - - - Id of the Device (mac address) + + + + Terminal of the Measurement + + + + + DATE_MIN + + + + Type of Measurement + + + + + + + + TEMP + + + + Observation period + + + + + ONE_WEEK - + Id of the Device (mac address) - - - - Id of the Module - - - - - Id of the main device + + + + UUID of the thing - + - - Id of the Module - - - - - Id of the main device - - - - - Default duration of thermostat change when force to max or manual. - 60 - true - - - - - - - UUID of the home + + Id of the Device (mac address) @@ -132,28 +143,5 @@ - - - - Camera MAC Address - - - - - UUID of the home hosting the camera - - - - - - - UUID of the Person - - - - - UUID of the home - - 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 index 0000000000000..117a048cf6198 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties @@ -0,0 +1,50 @@ +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 + +thing-type.netatmo.NAMain.label = Main Indoor Station +thing-type.netatmo.NAMain.description = This represents the main indoor module capable of reporting temperature,humidity,pressure,air quality and sound level + +thing-type.netatmo.NAModule1.label = Outdoor Module +thing-type.netatmo.NAModule1.description = This represents the outdoor module capable of reporting temperature and humidity + +thing-type.netatmo.NAModule2.label = Wind Gauge Module +thing-type.netatmo.NAModule2.description = This represents the wind module capable of reporting wind angle and strength + +thing-type.netatmo.NAModule3.label = Rain Gauge +thing-type.netatmo.NAModule3.description = This represents the Rain Gauge capable of measuring precipitation + +thing-type.netatmo.NAModule4.label = Additional Module +thing-type.netatmo.NAModule4.description = This represents an additional indoor module capable of reporting temperature, humidity and CO2 level + +thing-type.netatmo.NHC.label = Healthy Home Coach +thing-type.netatmo.NHC.description = This represents the healthy home coach capable of reporting health-index,temperature,humidity,pressure,air quality and sound level + +thing-type.netatmo.NAHome.label = Welcome Home +thing-type.netatmo.NAHome.description = This represents a home hosting Netatmo Security devices + +thing-type.netatmo.NAHomeEnergy.label = Welcome Home Energy +thing-type.netatmo.NAHomeEnergy.description = This represents a home hosting Netatmo Energy devices + +thing-type.netatmo.NAPlug.label = relay +thing-type.netatmo.NAPlug.description = The relay device controlling a Thermostat and zero or more valves + +thing-type.netatmo.NRV.label = Radiator valve +thing-type.netatmo.NRV.description = A valve controlling a radiator + +thing-type.netatmo.NARoom.label = Room +thing-type.netatmo.NARoom.description = Room + +thing-type.netatmo.NAHomeSecurity.label = Welcome Home Security +thing-type.netatmo.NAHomeSecurity.description = This represents a home hosting Netatmo Security / Weather devices + +actionLabel=send a set room thermpoint command +actionDesc=Send set room thermpoint command with endtime. + +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) 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 index 8c4289a458575..84cb420a8937c 100644 --- 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 @@ -59,8 +59,11 @@ thing-type.netatmo.NAModule4.description = Das Zusatz-Indoor-Modul liefert Daten 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.NRV.label = Heizkörper Ventil +thing-type.netatmo.NRV.description = Das Ventil steuert den Heizkörper. + +thing-type.netatmo.NATherm1.label = Thermostat +thing-type.netatmo.NATherm1.description = Das Thermostat dient zur Steuerung von Räumen 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. @@ -145,14 +148,14 @@ channel-type.netatmo.humidity.description = Zeigt die aktuelle Luftfeuchtigkeit 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.heat-index.label = Gef. Temperatur (Hitzeindex) +channel-type.netatmo.heat-index.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.dew-point.label = Taupunkt +channel-type.netatmo.dew-point.description = Zeigt den Taupunkt an. -channel-type.netatmo.dewPointDepression.label = Taupunktsdifferenz -channel-type.netatmo.dewPointDepression.description = Zeigt die Taupunktsdifferenz an. +channel-type.netatmo.dew-point-depression.label = Taupunktsdifferenz +channel-type.netatmo.dew-point-depression.description = Zeigt die Taupunktsdifferenz an. channel-type.netatmo.noise.label = Sonometer channel-type.netatmo.noise.description = Zeigt die aktuelle Lautstärke an. @@ -160,17 +163,17 @@ 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.pressure-trend.label = Luftdrucktrend +channel-type.netatmo.pressure-trend.description = Zeigt den Luftdrucktrend an (z.B. "Steigend", "Stabil" oder "Fallend"). +channel-type.netatmo.pressure-trend.state.option.up = Steigend +channel-type.netatmo.pressure-trend.state.option.stable = Stabil +channel-type.netatmo.pressure-trend.state.option.down = Fallend -channel-type.netatmo.absolutePressure.label = Absoluter Luftdruck -channel-type.netatmo.absolutePressure.description = Zeigt den absoluten Luftdruck an. +channel-type.netatmo.absolute-pressure.label = Absoluter Luftdruck +channel-type.netatmo.absolute-pressure.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.last-seen.label = Letzte Übertragung +channel-type.netatmo.last-seen.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. @@ -190,55 +193,55 @@ channel-type.netatmo.rain1.description = Zeigt den kumulierten Niederschlag der 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.wind-angle.label = Windrichtung +channel-type.netatmo.wind-angle.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.wind-strength.label = Windgeschwindigkeit +channel-type.netatmo.wind-strength.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.gust-angle.label = Böen Richtung +channel-type.netatmo.gust-angle.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.gust-strength.label = Böen Geschwindigkeit +channel-type.netatmo.gust-strength.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.health-index.label = Health Index +channel-type.netatmo.health-index.description = Zeigt den Health Index (z.B. "Gesund", "Gut", "Angemessen", "Schlecht" oder "Ungesund") an. +channel-type.netatmo.health-index.state.option.0 = Gesund +channel-type.netatmo.health-index.state.option.1 = Gut +channel-type.netatmo.health-index.state.option.2 = Angemessen +channel-type.netatmo.health-index.state.option.3 = Schlecht +channel-type.netatmo.health-index.state.option.4 = Ungesund -#channel-type.netatmo.connectedBoiler.label = Plug Connected Boiler -#channel-type.netatmo.connectedBoiler.description = +#channel-type.netatmo.connected.label = Plug Connected Boiler +#channel-type.netatmo.connected.description = -#channel-type.netatmo.lastPlugSeen.label = Last Plug Seen -#channel-type.netatmo.lastPlugSeen.description = Last Plug Seen +#channel-type.netatmo.last-seen.label = Last Plug Seen +#channel-type.netatmo.last-seen.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.last-bilan.label = Available Bilan +#channel-type.netatmo.last-bilan.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.setpoint.label = Setpoint +#channel-type.netatmo.setpoint.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.th-mode.label = Setpoint Mode +#channel-type.netatmo.th-mode.description = Chosen setpoint_mode (program, away, hg, manual, off, max) +#channel-type.netatmo.th-mode.state.option.program = Following a weekly schedule +#channel-type.netatmo.th-mode.state.option.away = Applying the -away- temperature as defined by the user +#channel-type.netatmo.th-mode.state.option.frost_guard = Frost-guard +#channel-type.netatmo.th-mode.state.option.manual = Applying a manually set temperature setpoint +#channel-type.netatmo.th-mode.state.option.off = Currently off +#channel-type.netatmo.th-mode.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.relay-status.label = Heating status +#channel-type.netatmo.relay-status.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.orientation.label = Orientation +#channel-type.netatmo.orientation.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.setpoint-end.label = Setpoint end +#channel-type.netatmo.setpoint-end.description = Thermostat goes back to schedule after that timestamp. #channel-type.netatmo.homecity.label = City #channel-type.netatmo.homecity.description = City of the home @@ -261,8 +264,8 @@ channel-type.netatmo.healthindex.state.option.unhealthy = Ungesund #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.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) @@ -279,8 +282,8 @@ channel-type.netatmo.healthindex.state.option.unhealthy = Ungesund #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.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 @@ -291,20 +294,20 @@ channel-type.netatmo.healthindex.state.option.unhealthy = Ungesund #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.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.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.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-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-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 @@ -312,8 +315,8 @@ channel-type.netatmo.healthindex.state.option.unhealthy = Ungesund #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.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.person_eventmsg.label = Last Event message #channel-type.netatmo.person_eventmsg.description = Last Event message from 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 index 84d40b659ffad..7fc0415b47f39 100644 --- 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 @@ -39,8 +39,8 @@ thing-type.config.netatmo.bridge.reconnectInterval.label = Intervalle de reconne 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.NAMain.label = Module intérieur principal +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é. @@ -69,8 +69,8 @@ thing-type.netatmo.NAWelcomeHome.description = Cet élément représente une mai 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.netatmo.NAPerson.label = Personne +thing-type.netatmo.NAPerson.description = Cet élément représente une personne de la maison. # thing type configuration thing-type.config.netatmo.station.id.label = ID équipement @@ -149,14 +149,14 @@ 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.heat-index.label = Indice de chaleur +channel-type.netatmo.heat-index.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.dew-point.label = Point de rosée +channel-type.netatmo.dew-point.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.dew-point-depression.label = Dépression du point de rosée +channel-type.netatmo.dew-point-depression.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 @@ -182,64 +182,64 @@ channel-type.netatmo.rain1.description = Volume de précipitations relevé duran 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.wind-angle.label = Direction du vent +channel-type.netatmo.wind-angle.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.wind-strength.label = Force du vent +channel-type.netatmo.wind-strength.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.gust-angle.label = Direction rafale de vent +channel-type.netatmo.gust-angle.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.gust-strength.label = Force rafale de vent +channel-type.netatmo.gust-strength.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.last-seen.label = Dernière demande d'état +channel-type.netatmo.last-seen.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.connected.label = Relais Connecté +channel-type.netatmo.connected.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.last-seen.label = Horodatage visibilité du relais +channel-type.netatmo.last-seen.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.last-bilan.label = Bilan Economies d'Energie +channel-type.netatmo.last-bilan.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.setpoint.label = Température de consigne +channel-type.netatmo.setpoint.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.th-mode.label = Mode de consigne +channel-type.netatmo.th-mode.description = Mode de consigne choisi sur le thermostat (planning hebdo, absence, hors-gel, manuel, arrêt, en permanence) +channel-type.netatmo.th-mode.state.option.program = Suivi du planning hebdomadaire +channel-type.netatmo.th-mode.state.option.away = Température d'absence +channel-type.netatmo.th-mode.state.option.frost_guard = Hors-gel +channel-type.netatmo.th-mode.state.option.manual = Température de consigne manuelle +channel-type.netatmo.th-mode.state.option.off = Arrêt +channel-type.netatmo.th-mode.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.relay-status.label = Etat du chauffage +channel-type.netatmo.relay-status.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.orientation.label = Orientation +channel-type.netatmo.orientation.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.setpoint-end.label = Heure fin de consigne +channel-type.netatmo.setpoint-end.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.health-index.label = Indice de confort +channel-type.netatmo.health-index.description = Indice de confort (sain, agréable, correct, mauvais, malsain) +channel-type.netatmo.health-index.state.option.0 = Sain +channel-type.netatmo.health-index.state.option.1 = Agréable +channel-type.netatmo.health-index.state.option.2 = Correct +channel-type.netatmo.health-index.state.option.3 = Mauvais +channel-type.netatmo.health-index.state.option.4 = Malsain channel-type.netatmo.homecity.label = Ville channel-type.netatmo.homecity.description = Ville @@ -262,8 +262,8 @@ 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.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 @@ -280,8 +280,8 @@ channel-type.netatmo.video_url.description = Url de la vidéo capturée (quand u 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.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 @@ -292,20 +292,20 @@ channel-type.netatmo.sub_type.description = Sous-type du dernier évènement (di 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.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.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.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-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-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 @@ -313,8 +313,8 @@ channel-type.netatmo.live_stream_url.description = Url du flux vidéo en direct 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.at-home.label = A la maison +channel-type.netatmo.at-home.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 @@ -336,43 +336,43 @@ channel-type.netatmo.person_event_url.description = URL de l'mage associée au d # 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 +thing-type.netatmo.NAMain.channel.signal-strength.label = Niveau Wifi +thing-type.netatmo.NAMain.channel.signal-strength.description = Indicateur de la qualité de signal Wifi + +thing-type.netatmo.NAPlug.channel.signal-strength.label = Niveau Wifi +thing-type.netatmo.NAPlug.channel.signal-strength.description = Indicateur de la qualité de signal Wifi + +thing-type.netatmo.NAModule1.channel.signal-strength.label = Niveau Radio +thing-type.netatmo.NAModule1.channel.signal-strength.description = Indicateur de la qualité de signal radio +thing-type.netatmo.NAModule1.channel.battery-level.label = Niveau piles +thing-type.netatmo.NAModule1.channel.battery-level.description = Indicateur du niveau de piles +thing-type.netatmo.NAModule1.channel.low-battery.label = Piles faibles +thing-type.netatmo.NAModule1.channel.low-battery.description = Indicateur du niveau faible des piles + +thing-type.netatmo.NAModule2.channel.signal-strength.label = Niveau Radio +thing-type.netatmo.NAModule2.channel.signal-strength.description = Indicateur de la qualité de signal radio +thing-type.netatmo.NAModule2.channel.battery-level.label = Niveau piles +thing-type.netatmo.NAModule2.channel.battery-level.description = Indicateur du niveau de piles +thing-type.netatmo.NAModule2.channel.low-battery.label = Piles faibles +thing-type.netatmo.NAModule2.channel.low-battery.description = Indicateur du niveau faible des piles + +thing-type.netatmo.NAModule3.channel.signal-strength.label = Niveau Radio +thing-type.netatmo.NAModule3.channel.signal-strength.description = Indicateur de la qualité de signal radio +thing-type.netatmo.NAModule3.channel.battery-level.label = Niveau piles +thing-type.netatmo.NAModule3.channel.battery-level.description = Indicateur du niveau de piles +thing-type.netatmo.NAModule3.channel.low-battery.label = Piles faibles +thing-type.netatmo.NAModule3.channel.low-battery.description = Indicateur du niveau faible des piles + +thing-type.netatmo.NAModule4.channel.signal-strength.label = Niveau Radio +thing-type.netatmo.NAModule4.channel.signal-strength.description = Indicateur de la qualité de signal radio +thing-type.netatmo.NAModule4.channel.battery-level.label = Niveau piles +thing-type.netatmo.NAModule4.channel.battery-level.description = Indicateur du niveau de piles +thing-type.netatmo.NAModule4.channel.low-battery.label = Piles faibles +thing-type.netatmo.NAModule4.channel.low-battery.description = Indicateur du niveau faible des piles + +thing-type.netatmo.NATherm1.channel.signal-strength.label = Niveau Radio +thing-type.netatmo.NATherm1.channel.signal-strength.description = Indicateur de la qualité de signal radio +thing-type.netatmo.NATherm1.channel.battery-level.label = Niveau piles +thing-type.netatmo.NATherm1.channel.battery-level.description = Indicateur du niveau de piles +thing-type.netatmo.NATherm1.channel.low-battery.label = Piles faibles +thing-type.netatmo.NATherm1.channel.low-battery.description = Indicateur du niveau faible des piles 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/aircare.xml similarity index 60% rename from bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/bridge.xml rename to bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/aircare.xml index b3591dfe04ede..0bda3455244e9 100644 --- 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/aircare.xml @@ -4,11 +4,11 @@ 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"> - - - - This bridge represents the gateway to Netatmo API. - - + + + + + + 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 index d834b66ea5377..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/camera.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - This represents a welcome camera at home - - - - - - - - - - - - - id - - - - - - - - - - This represents a presence camera at home - - - - - - - - - - - - - - id - - - - - - Switch - - State of the camera - - - - Switch - - State of the SD card - - - - - Switch - - State of the power connector - - - - - Switch - - Only for scope access_camera. If Camera and application requesting the information are on the same - network (true/false) - - - - - String - - Url of the live snapshot for this camera (need scope access_camera) - - - - - Image - - Camera Live Snapshot - - - - - String - - Url of the live stream for this camera - - - - - Switch - - State of the floodlight auto-mode - - - - Switch - - State of the floodlight - - - diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/channels.xml index a535a74a26a2f..f4c25c175a0be 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/channels.xml @@ -4,476 +4,185 @@ 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"> - - DateTime - - Last Status Store - Time + + Switch + + + + + Switch + - - Location - - Location of the device - + + Image + + Event Snapshot + - - Number:Temperature - - Current temperature - Temperature - + + trigger + + + + + + + + + - - Number:Temperature - - Minimum Temperature on current day - Temperature - + + String + + - - Number:Temperature - - Minimum Temperature this week - Temperature - + + String + + - - Number:Temperature - - Minimum Temperature this month - Temperature - + + Number + + Total number of Persons that are at home + - - Number:Temperature - - Maximum Temperature on current day - Temperature + + Number:Length + + Quantity of water over the period + Rain - - Number:Temperature - - Maximum Temperature this week - Temperature + + Number:Speed + + Precipitation intensity + Rain - - Number:Temperature - - Maximum Temperature this month - Temperature - + + Number:Time + + Default duration of manual setpoint changes. + time + - + String - - Temperature Evolution Trend - Temperature + + Line - - - + + + + + + + + + String + + Status of the video (recording, deleted or available) + + + + + - + Number:Temperature Thermostat temperature setpoint Temperature - + - + String - - Chosen setpoint_mode (program, away, hg, manual, off, max) + + Chosen thermostat mode (home, frost guard, manual, max) - - - - - - + + + + - - Switch - - Indicates whether the furnace is heating or not - + + String + + Chosen mode for the house (schedule, away, frost guard, manual) + + + + + + + + - - Number + + Number:Dimensionless + + Percentage of heating power. + Energy + + + + + Number:Angle Physical orientation of the thermostat module - + niveau + - + DateTime Timestamp when data was measured - Time - - - - - DateTime - - Last Plug Seen - Time - - - - - DateTime - - Date when minimum CO2 was reached on current day - Time - - - - - DateTime - - Date when minimum CO2 was reached this week - Time - - - - - DateTime - - Date when minimum CO2 was reached this month - Time - - - - - DateTime - - Date when maximum CO2 was reached on current day - Time - - - - - DateTime - - Date when maximum CO2 was reached this week - Time - - - - - DateTime - - Date when maximum CO2 was reached this month - Time - - - - - DateTime - - Date when minimum temperature was reached on current day - Time - - - - - DateTime - - Date when minimum temperature was reached this week - Time - - - - - DateTime - - Date when minimum temperature was reached this month - Time - - - - - DateTime - - Date when maximum temperature was reached on current day - Time - - - - - DateTime - - Date when maximum temperature was reached this week - Time - - - - - DateTime - - Date when maximum temperature was reached this month - Time - - - - - DateTime - - Date when minimum humidity was reached on current day - Time - - - - - DateTime - - Date when minimum humidity was reached this week - Time - - - - - DateTime - - Date when minimum humidity was reached this month - Time - - - - - DateTime - - Date when maximum humidity was reached on current day - Time - - - - - DateTime - - Date when maximum humidity was reached this week - Time - - - - - DateTime - - Date when maximum humidity was reached this month - Time - - - - - DateTime - - Date when minimum noise was reached on current day - Time - - - - - DateTime - - Date when minimum noise was reached this week - Time - - - - - DateTime - - Date when minimum noise was reached this month - Time - - - - - DateTime - - Date when maximum noise was reached on current day - Time - - - - - DateTime - - Date when maximum noise was reached this week - Time - - - - - DateTime - - Date when maximum noise was reached this month - Time - - - - - DateTime - - Date when minimum pressure was reached on current day - Time - - - - - DateTime - - Date when minimum pressure was reached this week - Time - - - - - DateTime - - Date when minimum pressure was reached this month - Time - - - - - DateTime - - Date when maximum pressure was reached on current day - Time - - - - - DateTime - - Date when maximum pressure was reached this week - Time - - - - - DateTime - - Date when maximum pressure was reached this month - Time + time - + DateTime Month of the last available thermostat bilan - Time + time - - Switch + + Contact - - DateTime - - Last Message emitted by the module - Time - - - - - DateTime - - Thermostat goes back to schedule after that timestamp. - Time - - - - - DateTime - - Last Them Seen - Time - - - Number:Dimensionless Air Quality - Carbondioxide - - - - - Number:Dimensionless - - Minimum CO2 on current day - Carbondioxide - - - - - Number:Dimensionless - - Minimum CO2 this week - Carbondioxide - - - - - Number:Dimensionless - - Minimum CO2 this month - Carbondioxide - - - - - Number:Dimensionless - - Maximum CO2 on current day - Carbondioxide - - - - - Number:Dimensionless - - Maximum CO2 this week - Carbondioxide - - - - - Number:Dimensionless - - Maximum CO2 this month - Carbondioxide + carbondioxide @@ -481,360 +190,128 @@ Number:Dimensionless Current Noise Level - Noise - - - - - Number:Dimensionless - - Minimum Noise on current day - Noise + soundvolume - - Number:Dimensionless - - Minimum Noise this week - Noise - - - - - Number:Dimensionless - - Minimum Noise this month - Noise - - - - - Number:Dimensionless - - Maximum Noise on current day - Noise - - - - - Number:Dimensionless - - Maximum Noise this week - Noise - - - - - Number:Dimensionless - - Maximum Noise this month - Noise - - - - - String + + Number Health Index (healthy, fine, fair, poor, unhealthy) - + - - - - - + + + + + - - Number:Pressure - - Current pressure - Pressure - - - - - Number:Pressure - - Minimum Pressure on current day - Pressure - - - - - Number:Pressure - - Minimum Pressure this week - Pressure - - - - - Number:Pressure - - Minimum Pressure this month - Pressure - - - - - Number:Pressure - - Maximum Pressure on current day - Pressure - - - - - Number:Pressure - - Maximum Pressure this week - Pressure - - - - - Number:Pressure - - Maximum Pressure this month - Pressure - - - - - String - - Pressure evolution trend for last 12h (up, down, stable) - Pressure - - - - - - - - - - - String - - Heat planning currently used - - - - - Number:Pressure - - Absolute pressure - Pressure - - - - - Number:Dimensionless - - Current humidity - Humidity - - - - - Number:Dimensionless - - Minimum Humidity on current day - Humidity - - - - - Number:Dimensionless - - Minimum Humidity this week - Humidity - - - - - Number:Dimensionless - - Minimum Humidity this month - Humidity - - - - - Number:Dimensionless - - Maximum Humidity on current day - Humidity - - - - - Number:Dimensionless - - Minimum Humidity this week - Humidity - - - - - Number:Dimensionless - - Maximum Humidity this month - Humidity - - - Number Computed Humidex index - Temperature - - Number:Temperature - - Computed Heat Index - Temperature - - - - - Number:Temperature - - Computed Dewpoint Temperature - Temperature - - - - - Number:Temperature - - Computed Dewpoint Depression - - - - - Number:Length - - Quantity of water - Rain - - - - - Number:Length - - Quantity of water on last hour - Rain - - - - - Number:Length - - Quantity of water on last day - Rain - - - - - Number:Length - - Quantity of water this week - Rain - - - - - Number:Length - - Quantity of water this month - Rain - - - - - Number:Angle - - Current 5 minutes average wind direction - Wind - - - - - Number:Speed - - Current 5 minutes average wind speed - Wind - - - - - Number:Speed - - Maximum wind strength recorded - Wind - - - - - DateTime - - Timestamp when MaxWindStrength was recorded. - Time - + + Number + + Computed Humidex index + + + + + + + + + - - Number:Angle - - Direction of the last 5 minutes highest gust wind - Wind - + + String + + Description of the event + + + + + + + + + + + + + + + + + + + + - - Number:Speed - - Speed of the last 5 minutes highest gust wind - Wind - + + String + + Details of the event + + + + + + + + + + + + + + + + + - + trigger Home event - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + Number:Power + + Wi-Fi signal strength indicator. + QualityOfService + + + 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 index 0000000000000..702da9c8d7e94 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/common.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Last time this element has been seen + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/configurable-channels.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/configurable-channels.xml new file mode 100644 index 0000000000000..e0e263b24204d --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/configurable-channels.xml @@ -0,0 +1,41 @@ + + + + + Number:Length + + Quantity of water over a given period + Rain + + + + + + Number:Dimensionless + + + + + + + DateTime + + Timestamp when data was measured + time + + + + + + Number:Time + + History of the boiler data + time + + + + + 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 index 0000000000000..570579f389d2f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/energy.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + Temperature of the room + + + + Timestamp when data was measured + + + + + + + + + + Measured temperature of the room + + + + + + + + + + Indicates whether window open deteced or not + + + + + + + + + + + + + + + + + + Timestamp a manual setpoint began + + + + Thermostat goes back to schedule after that timestamp. + + + + + + + + + + + Indicates whether the furnace is heating or not + + + + + + + + + + + + + + + Heat planning currently used + + + + Current mode (eg. frost-guard or away) + + + + Timestamp until current mode is active + + + + + + + + + + Status of battery + + + + + 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 index 1d4a4ed0b0b7b..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/healthyhomecoach.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - This represents the healthy home coach capable of reporting health - index,temperature,humidity,pressure,air quality and sound level - - - - - - - - - - - - - - - - - - - - - - - 99,84,69,54 - auto - - - id - - - 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 index 0000000000000..0173a30a35bc9 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/security.xml @@ -0,0 +1,153 @@ + + + + + + + + + + Count how many Unknown Persons are at home + + + + City of the home + + + + Country of the home + + + + Timezone of the home + + + + + + + + + + + Monitoring state of the camera + + + + State of the SD card + + + + State of the power connector + + + + Camera Live Snapshot + + + + Url of the live snapshot for this camera (need scope access_camera) + + + + Url of the live stream for this camera + + + + + + + + + + + + Id of the person the event is about (if any) + + + + + Last Event message from this person + + + + Last Event message time for this person + + + + Picture of the last event for this person + + + + URL for the picture of the last event for this person + + + + URL for the stream of the last event for this person + + + + + + + + + + + Last Event message time for this person + + + + Picture of the last event for this person + + + + URL for the picture of the last event for this person + + + + Id of the camera that generates the event + + + + + + + + + + URL for the avatar of this person + + + + Avatar of this person + + + + Indicates if this person is known to be at home or not + + + + Last time this element has been seen + + + + + + + + + + State of the floodlight auto-mode + + + + State of the floodlight + + + + + 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 index aa19c9ddcb429..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/station.xml +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - - - - - This represents the main indoor module capable of reporting temperature,humidity,pressure,air quality and - sound level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 99,84,69,54 - auto - - - id - - - - - - - - - - This represents the outdoor module capable of reporting temperature and humidity - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 90,80,70,60 - 3600,4500,6000 - - - id - - - - - - - - - - This represents the wind module capable of reporting wind angle and strength - - - - - - - - - - - - - - - - - - 90,80,70,60 - 3950,4770,6000 - - - id - - - - - - - - - - This represents the Rain Gauge capable of measuring precipitation - - - - - - - - - - - - - - - - - 90,80,70,60 - 3600,4500,6000 - - - id - - - - - - - - - - This represents an additional indoor module capable of reporting temperature, humidity and CO2 level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 90,80,70,60 - 4200,4920,6000 - - - id - - - - 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 index b998b8738cde3..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/thermostat.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - This represents the thermostat relay - - - - - - - - - - - - - 99,84,69,54 - 3600000 - - - id - - - - - - - - - - This represents the thermostat module itself - - - - - - - - - - - - - - - - - - - - 90,80,70,60 - 2700,3300,4500 - - - id - - - - 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 index 0000000000000..841cad499da33 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/weather.xml @@ -0,0 +1,126 @@ + + + + + + + + + Current Temperature + + + + Minimum Temperature on current day + + + + Maximum Temperature on current day + + + + Moment when minimum temperature was reached on current day + + + + Moment when maximum temperature was reached on current day + + + + Temperature Evolution Trend + + + + + + + + + + + + + Computed Heat Index + + + + Computed Dewpoint Temperature + + + + Computed Dewpoint Depression + + + + + + + + + + + + + + + + + + + + + + + + + Absolute pressure at sea level + + + + Pressure evolution trend for last 12h (up, down, stable) + + + + + + + + + + + Current hourly total + + + + Current daily total + + + + + + + + + + + + Maximum wind strength recorded + + + + Date maximum wind strength recorded + + + + Direction of the last 5 minutes highest gust wind + + + + Speed of the last 5 minutes highest gust wind + + + + + 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 index 9c6b59e1a546b..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/welcomehome.xml +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - - - This represents a home hosting a camera - - - - - - - - - - - - - - - - - - - - - - - - - - id - - - - - String - - City of the home - - - - String - - Country of the home - - - - String - - Timezone of the home - - - - Number - - Total number of Persons that are at home - - - - Number - - Count how many Unknown Persons are at home - - - - - String - - Type of event. Go to the Welcome page for further details. - - - - DateTime - - Time of occurrence of event - - - - String - - Camera that detected the event - - - - String - - Id of the person the event is about (if any) - - - - String - - Url of the event snapshot - - - - - Image - - Event Snapshot - - - - - String - - URL of the event video - - - - String - - Status of the video (recording, deleted or available) - - - - Switch - - If person was considered "away" before being seen during this event - - - - String - - Message sent by Netatmo corresponding to given event - - - - String - - Sub-type of SD and Alim events. Go to Welcome page for further details. - - - - - trigger - - - - - - - - - - - - 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 index ecf3eaf6f7c14..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/welcomeperson.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - This represents a person at home - - - - - - - - - - - - - - id - - - - - DateTime - - Time when this person was last seen - - - - - Switch - - Indicates if this person is known to be at home or not - - - - String - - Last Event message from this person - - - - - DateTime - - Last Event message time for this person - - - - - String - - URL for the avatar of this person - - - - - Image - - Avatar of this person - - - - - Image - - Picture of the last event for this person - - - - - String - - URL for the picture of the last event for this person - - - - 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 index 0000000000000..c47969aafaa93 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/dto/NAObjectTest.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2010-2021 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.Assert.assertEquals; +import static org.mockito.Mockito.*; + +import java.time.ZoneId; +import java.util.Hashtable; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.openhab.binding.netatmo.internal.api.ApiBridge; +import org.openhab.binding.netatmo.internal.api.NetatmoConstants.TrendDescription; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.core.auth.client.oauth2.OAuthFactory; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.osgi.service.component.ComponentContext; + +/** + * @author Gaël L'hopital - Initial contribution + */ +public class NAObjectTest { + private static ApiBridge apiBridge; + + @BeforeAll + public static void init() { + ComponentContext componentContext = mock(ComponentContext.class); + when(componentContext.getProperties()).thenReturn(new Hashtable<>()); + OAuthFactory oAuthFactory = mock(OAuthFactory.class); + HttpClientFactory httpClientFactory = mock(HttpClientFactory.class); + TimeZoneProvider timeZoneProvider = mock(TimeZoneProvider.class); + when(timeZoneProvider.getTimeZone()).thenReturn(ZoneId.systemDefault()); + apiBridge = new ApiBridge(oAuthFactory, httpClientFactory, timeZoneProvider, componentContext); + } + + @Test + public void testNAObject() throws Exception { + String naObject = "{id:\"5954e7f249c75f97428b7b23\",name:\"Your House\"}"; + NAObject object = apiBridge.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\"" + "}"; + NAWebhookEvent object = apiBridge.deserialize(NAWebhookEvent.class, event); + NASnapshot snap = object.getSnapshot(); + assertEquals("5d19bxxxxxx6380342", snap.getId()); + } + + @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\"}"; + NADashboard object = apiBridge.deserialize(NADashboard.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 index a9b392613ce92..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryServiceTest.java +++ /dev/null @@ -1,282 +0,0 @@ -/** - * Copyright (c) 2010-2021 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.WARN) -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 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 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 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 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 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 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 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 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 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 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 index 08b05e279040f..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/presence/NAPresenceCameraHandlerTest.java +++ /dev/null @@ -1,494 +0,0 @@ -/** - * Copyright (c) 2010-2021 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.WARN) -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 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 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 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 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 streamURL = handler.getStreamURL("dummyVideoId"); - assertTrue(streamURL.isPresent()); - assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index.m3u8", streamURL.get()); - } - - @Test - public void testGetStreamURLWithoutVPN() { - Optional 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 createPingResponseContent(final String localURL) { - return Optional.of("{\"local_url\":\"" + localURL + "\",\"product_name\":\"Welcome Netatmo\"}"); - } - - private interface RequestExecutor { - - Optional 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 index caa425d3b5317..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandlerTest.java +++ /dev/null @@ -1,372 +0,0 @@ -/** - * Copyright (c) 2010-2021 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.WARN) -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 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 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; - } - } -}