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..9dccea907597a 100644 --- a/bundles/org.openhab.binding.netatmo/README.md +++ b/bundles/org.openhab.binding.netatmo/README.md @@ -9,25 +9,12 @@ The Netatmo binding integrates the following Netatmo products: See https://www.netatmo.com/ for details on their product. -Please note, recent Netatmo thermostats are not supported because they require the Energy API which is not yet implemented in the binding. -Only older Netatmo thermostats compatible with the Thermostat API are supported. -For the same reason, Netatmo valves are also not supported. - - ## Binding Configuration -The binding has the following configuration options: - -| Parameter | Name | Description | -|---------------------|----------------------|-----------------------------------| -| backgroundDiscovery | Background Discovery | If set to true, the device and its associated modules are updated in the discovery inbox at each API call run to refresh device data. Default is false. | - Before setting up your 'Things', you will have to grant openHAB to access Netatmo API. Here is the procedure: -### 1. Application Creation - -Create an application at https://dev.netatmo.com/apps/createanapp +Create an application at https://dev.netatmo.com/dev/createapp The variables you will need to get to setup the binding are: @@ -36,49 +23,73 @@ The variables you will need to get to setup the binding are: * `` The username you use to connect to the Netatmo API (usually your mail address). * `` The password attached to the above username. +The binding has the following configuration options: -### 2. Bridge and Things Configuration +| Parameter | Type | Description | +|--------------|---------------|--------------------------------------------------------------------------------------------| +| features | String | The perimeter of functionalities given to the binding WEATHER, AIR_CARE, ENERGY, SECURITY | +| readFriends | Boolean | Enables or disables the discovery of guest weather stations. | -Once you will get needed informations from the Netatmo API, you will be able to configure bridge and things. -E.g. +## Bridge Configuration + +You will have to create at first a bridge to handle communication with your Netatmo Application. + +The Account bridge has the following configuration options: + +- **clientId:** Client ID provided for the application you created on http://dev.netatmo.com/createapp. +- **clientSecret:** Client Secret provided for the application you created. +- **username:** Your Netatmo API username (email). +- **password:** Your Netatmo API password. +- **webHookUrl:** Protocol, public IP and port to access openHAB server from Internet. +- **reconnectInterval:** The reconnection interval to Netatmo API (in s). + + +## List of supported things + +| Thing Type | Type | Netatmo Object | Description | Thing Parameters | +|-----------------|--------|----------------|------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| +| account | Bridge | N/A | This bridge represents an account, gateway to Netatmo API. | clientId, clientSecret, username, password, webHookUrl, reconnectInterval | +| home | Bridge | NAHome | A home hosting Security or Energy devices and modules. | id, refreshInterval | +| person | Thing | NAPerson | A person known by your Netatmo system. | id | +| welcome | Thing | NACamera | The Netatmo Smart Indoor Camera (Welcome). | id | +| presence | Thing | NOC | The Netatmo Smart Outdoor Camera (Presence) camera with or without siren. | id | +| siren | Thing | NIS | The Netatmo Smart Indoor Siren. | id | +| doorbell | Thing | NDB | The Netatmo Smart Video Doorbell device. | id | +| weather-station | Bridge | NAMain | Main indoor module reporting temperature, humidity, pressure, air quality and sound level. | id | +| outdoor | Thing | NAModule1 | Outdoor module reporting temperature and humidity. | id | +| wind | Thing | NAModule2 | Wind sensor reporting wind angle and strength. | id | +| rain | Thing | NAModule3 | Rain Gauge measuring precipitation. | id | +| indoor | Thing | NAModule4 | Additional indoor module reporting temperature, humidity and CO2 level. | id | +| home-coach | Thing | NHC | Healthy home coach reporting health-index, temperature, humidity, pressure, air quality, sound level.| id | +| plug | Thing | NAPlug | The relay connected to the boiler controlling a Thermostat and zero or more valves. | id | +| thermostat | Thing | NATherm1 | The Thermostat device placed in a given room. | id | +| room | Thing | NARoom | A room in your house. | id | +| valve | Thing | NRV | A valve controlling a radiator. | id | + -``` -Bridge netatmo:netatmoapi:home [ clientId="", 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" ] - ... -} -``` ### Webhook -For Welcome or Presence Camera, Netatmo servers can send push notifications to the Netatmo Binding by using a callback URL. -The webhook URL is setup at bridge level using "Webhook Address" parameter. +Netatmo servers can send push notifications to the Netatmo Binding by using a callback URL. +The webhook URL is setup at binding level using "Webhook Address" parameter. You will define here public way to access your openHAB server: ``` -http(s)://xx.yy.zz.ww:8080 +http(s)://xx.yy.zz.ww:443 ``` -Your Netatmo App will be configured automatically by the bridge to the endpoint : +Your Netatmo App will be configured automatically by the bridge to the endpoint: ``` -http(s)://xx.yy.zz.ww:8080/netatmo/%id%/camera +http(s)://xx.yy.zz.ww:443/netatmo ``` -where %id% is the id of your camera thing. - Please be aware of Netatmo own limits regarding webhook usage that lead to a 24h ban-time when webhook does not answer 5 times. +NB: Allowed ports for webhooks are 80, 88, 443 and 9443. + ### Configure Things @@ -87,7 +98,7 @@ First login with your user. Then some examples of the documentation contain the **real results** of your weather station. In order to try the examples, you need the `device_id` of your Netatmo station. You can find it in the configuration menu of the app (android or apple). -Get the IDs of your devices (indoor, outdoor, rain gauge) +Get the IDs of your devices (indoor, outdoor, rain gauge) [here](https://dev.netatmo.com/resources/technical/reference/weather/getstationsdata). `main_device` is the ID of the "main device", the indoor sensor. @@ -111,7 +122,7 @@ For example your serial number "h00bcdc" should end up as "02:00:00:00:bc:dc". ## Discovery -If you did not manually create things in the *.things file, the Netatmo Binding is able to discover automatically all depending modules and devices from Netatmo website. +If you did not manually create things in the *.things file, the Netatmo Binding is able to discover automatically all depending modules and devices. ## Channels @@ -122,297 +133,173 @@ If you did not manually create things in the *.things file, the Netatmo Binding Weather station does not need any refreshInterval setting. Based on a standard update period of 10mn by Netatmo systems - it will auto adapt to stick closest as possible to last data availability. -Example item for the **indoor module**: - -``` -Number Netatmo_Indoor_CO2 "CO2" { channel = "netatmo:NAMain:home:inside:Co2" } -``` **Supported channels for the main indoor module:** -| Channel ID | Item Type | Description | -|---------------------|----------------------|----------------------------------------------------------| -| Co2 | Number:Dimensionless | Air quality | -| MinCo2 | Number:Dimensionless | Minimum CO2 on current day | -| MinCo2ThisWeek | Number:Dimensionless | Minimum CO2 this week | -| MinCo2ThisMonth | Number:Dimensionless | Minimum CO2 this month | -| MaxCo2 | Number:Dimensionless | Maximum CO2 on current day | -| MaxCo2ThisWeek | Number:Dimensionless | Maximum CO2 this week | -| MaxCo2ThisMonth | Number:Dimensionless | Maximum CO2 this month | -| DateMinCo2 | DateTime | Date when minimum CO2 was reached on current day | -| DateMinCo2ThisWeek | DateTime | Date when minimum CO2 was reached this week | -| DateMinCo2ThisMonth | DateTime | Date when minimum CO2 was reached this month | -| DateMaxCo2 | DateTime | Date when maximum CO2 was reached on current day | -| DateMaxCo2ThisWeek | DateTime | Date when maximum CO2 was reached this week | -| DateMaxCo2ThisMonth | DateTime | Date when maximum CO2 was reached this month | -| Temperature | Number:Temperature | Current temperature | -| TempTrend | String | Temperature evolution trend (up, down, stable) | -| Noise | Number:Dimensionless | Current noise level | -| MinNoise | Number:Dimensionless | Minimum noise on current day | -| MinNoiseThisWeek | Number:Dimensionless | Minimum noise this week | -| MinNoiseThisMonth | Number:Dimensionless | Minimum noise this month | -| MaxNoise | Number:Dimensionless | Maximum noise on current day | -| MaxNoiseThisWeek | Number:Dimensionless | Maximum noise this week | -| MaxNoiseThisMonth | Number:Dimensionless | Maximum noise this month | -| DateMinNoise | DateTime | Date when minimum noise was reached on current day | -| DateMinNoiseThisWeek| DateTime | Date when minimum noise was reached this week | -| DateMinNoiseThisMonth| DateTime | Date when minimum noise was reached this month | -| DateMaxNoise | DateTime | Date when maximum noise was reached on current day | -| DateMaxNoiseThisWeek| DateTime | Date when maximum noise was reached this week | -| DateMaxNoiseThisMonth| DateTime | Date when maximum noise was reached this month | -| Pressure | Number:Pressure | Current pressure | -| MinPressure | Number:Pressure | Minimum pressure on current day | -| MinPressureThisWeek | Number:Pressure | Minimum pressure this week | -| MinPressureThisMonth| Number:Pressure | Minimum pressure this month | -| MaxPressure | Number:Pressure | Maximum pressure on current day | -| MaxPressureThisWeek | Number:Pressure | Maximum pressure this week | -| MaxPressureThisMonth| Number:Pressure | Maximum pressure this month | -| DateMinPressure | DateTime | Date when minimum pressure was reached on current day | -| DateMinPressureThisWeek | DateTime | Date when minimum pressure was reached this week | -| DateMinPressureThisMonth| DateTime | Date when minimum pressure was reached this month | -| DateMaxPressure | DateTime | Date when maximum pressure was reached on current day | -| DateMaxPressureThisWeek | DateTime | Date when maximum pressure was reached this week | -| DateMaxPressureThisMonth| DateTime | Date when maximum pressure was reached this month | -| PressTrend | String | Pressure evolution trend for last 12h (up, down, stable) | -| AbsolutePressure | Number:Pressure | Absolute pressure | -| Humidity | Number:Dimensionless | Current humidity | -| MinHumidity | Number:Dimensionless | Minimum humidity on current day | -| MinHumidityThisWeek | Number:Dimensionless | Minimum humidity this week | -| MinHumidityThisMonth| Number:Dimensionless | Minimum humidity this month | -| MaxHumidity | Number:Dimensionless | Maximum humidity on current day | -| MaxHumidityThisWeek | Number:Dimensionless | Maximum humidity this week | -| MaxHumidityThisMonth| Number:Dimensionless | Maximum humidity this month | -| DateMinHumidity | DateTime | Date when minimum humidity was reached on current day | -| DateMinHumidityThisWeek | DateTime | Date when minimum humidity was reached this week | -| DateMinHumidityThisMonth| DateTime | Date when minimum humidity was reached this month | -| DateMaxHumidity | DateTime | Date when maximum humidity was reached on current day | -| DateMaxHumidityThisWeek | DateTime | Date when maximum humidity was reached this week | -| DateMaxHumidityThisMonth| DateTime | Date when maximum humidity was reached this month | -| Humidex | Number | Computed Humidex index | -| HeatIndex | Number:Temperature | Computed Heat Index | -| Dewpoint | Number:Temperature | Computed dewpoint temperature | -| DewpointDepression | Number:Temperature | Computed dewpoint depression | -| MinTemp | Number:Temperature | Minimum temperature on current day | -| MinTempThisWeek | Number:Temperature | Minimum temperature this week | -| MinTempThisMonth | Number:Temperature | Minimum temperature this month | -| MaxTemp | Number:Temperature | Maximum temperature on current day | -| MaxTempThisWeek | Number:Temperature | Maximum temperature this week | -| MaxTempThisMonth | Number:Temperature | Maximum temperature this month | -| DateMinTemp | DateTime | Date when minimum temperature was reached on current day | -| DateMinTempThisWeek | DateTime | Date when minimum temperature was reached this week | -| DateMinTempThisMonth| DateTime | Date when minimum temperature was reached this month | -| DateMaxTemp | DateTime | Date when maximum temperature was reached on current day | -| DateMaxTempThisWeek | DateTime | Date when maximum temperature was reached this week | -| DateMaxTempThisMonth| DateTime | Date when maximum temperature was reached this month | -| DateMinTemp | DateTime | Date when minimum temperature was reached on current day | -| DateMaxTemp | DateTime | Date when maximum temperature was reached on current day | -| TimeStamp | DateTime | Timestamp when data was measured | -| LastStatusStore | DateTime | Last status store | -| WifiStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | -| Location | Location | Location of the device | +| Channel Group | Channel Id | Item Type | Description | +|---------------------|----------------------|----------------------|--------------------------------------------------| +| pressure | value | Number:Pressure | Current pressure | +| pressure | absolute | Number:Pressure | Pressure at sea level | +| pressure | trend | String | Pressure evolution trend over time | +| noise | value | Number:Dimensionless | Current noise level | +| humidity | value | Number:Dimensionless | Current humidity | +| humidity | humidex | Number | Computed Humidex index | +| humidity | humidex-scale | Number | Humidex index appreciation | +| temperature | value | Number:Temperature | Current temperature | +| temperature | min-today | Number:Temperature | Minimum temperature on current day | +| temperature | max-today | Number:Temperature | Maximum temperature on current day | +| temperature | min-time | DateTime | Moment of today's minimum temperature | +| temperature | max-time | DateTime | Moment of today's maximum temperature | +| temperature | trend | String | Temperature evolution trend over time | +| temperature | heat-index | Number:Temperature | Computed Heat Index | +| temperature | dewpoint | Number:Temperature | Computed dewpoint temperature | +| temperature | dewpoint-depression | Number:Temperature | Computed dewpoint depression | +| airquality | co2 | Number:Dimensionless | CO2 level in ppm | +| location | value | Location | Location of the device | +| timestamp | last-seen | DateTime | Last time the module reported its presence | +| timestamp | measures | DateTime | Moment of the last measures update | +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | All these channels are read only. ### Weather Station Outdoor module -Example item for the **outdoor module** - -``` -Number Netatmo_Outdoor_Temperature "Temperature" { channel = "netatmo:NAModule1:home:outside:Temperature" } -``` - **Supported channels for the outdoor module:** -| Channel ID | Item Type | Description | -|---------------------|----------------------|----------------------------------------------------------| -| Temperature | Number:Temperature | Current temperature | -| TempTrend | String | Temperature evolution trend (up, down, stable) | -| Humidity | Number:Dimensionless | Current humidity | -| MinHumidity | Number:Dimensionless | Minimum humidity on current day | -| MinHumidityThisWeek | Number:Dimensionless | Minimum humidity this week | -| MinHumidityThisMonth| Number:Dimensionless | Minimum humidity this month | -| MaxHumidity | Number:Dimensionless | Maximum humidity on current day | -| MaxHumidityThisWeek | Number:Dimensionless | Maximum humidity this week | -| MaxHumidityThisMonth| Number:Dimensionless | Maximum humidity this month | -| DateMinHumidity | DateTime | Date when minimum humidity was reached on current day | -| DateMinHumidityThisWeek | DateTime | Date when minimum humidity was reached this week | -| DateMinHumidityThisMonth| DateTime | Date when minimum humidity was reached this month | -| DateMaxHumidity | DateTime | Date when maximum humidity was reached on current day | -| DateMaxHumidityThisWeek | DateTime | Date when maximum humidity was reached this week | -| DateMaxHumidityThisMonth| DateTime | Date when maximum humidity was reached this month | -| Humidex | Number | Computed Humidex index | -| HeatIndex | Number:Temperature | Computed Heat Index | -| Dewpoint | Number:Temperature | Computed dewpoint temperature | -| DewpointDepression | Number:Temperature | Computed dewpoint depression | -| MinTemp | Number:Temperature | Minimum temperature on current day | -| MinTempThisWeek | Number:Temperature | Minimum temperature this week | -| MinTempThisMonth | Number:Temperature | Minimum temperature this month | -| MaxTemp | Number:Temperature | Maximum temperature on current day | -| MaxTempThisWeek | Number:Temperature | Maximum temperature this week | -| MaxTempThisMonth | Number:Temperature | Maximum temperature this month | -| DateMinTemp | DateTime | Date when minimum temperature was reached on current day | -| DateMinTempThisWeek | DateTime | Date when minimum temperature was reached this week | -| DateMinTempThisMonth| DateTime | Date when minimum temperature was reached this month | -| DateMaxTemp | DateTime | Date when maximum temperature was reached on current day | -| DateMaxTempThisWeek | DateTime | Date when maximum temperature was reached this week | -| DateMaxTempThisMonth| DateTime | Date when maximum temperature was reached this month | -| TimeStamp | DateTime | Timestamp when data was measured | -| LastMessage | DateTime | Last message emitted by the module | -| LowBattery | Switch | Low battery | -| BatteryVP | Number | Battery level | -| RfStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | +| Channel Group | Channel Id | Item Type | Description | +|---------------------|----------------------|----------------------|--------------------------------------------------| +| humidity | value | Number:Dimensionless | Current humidity | +| humidity | humidex | Number | Computed Humidex index | +| humidity | humidex-scale | Number | Humidex index appreciation | +| temperature | value | Number:Temperature | Current temperature | +| temperature | min-today | Number:Temperature | Minimum temperature on current day | +| temperature | max-today | Number:Temperature | Maximum temperature on current day | +| temperature | min-time | DateTime | Moment of today's minimum temperature | +| temperature | max-time | DateTime | Moment of today's maximum temperature | +| temperature | trend | String | Temperature evolution trend over time | +| temperature | heat-index | Number:Temperature | Computed Heat Index | +| temperature | dewpoint | Number:Temperature | Computed dewpoint temperature | +| temperature | dewpoint-depression | Number:Temperature | Computed dewpoint depression | +| timestamp | last-seen | DateTime | Last time the module reported its presence | +| timestamp | measures | DateTime | Moment of the last measures update | +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | +| battery | value | Number | Battery level | +| battery | low-battery | Switch | Low battery | All these channels are read only. ### Weather Station Additional Indoor module -Example item for the **indoor module** - -``` -Number Netatmo_Indoor2_Temperature "Temperature" { channel = "netatmo:NAModule4:home:insidesupp:Temperature" } -``` **Supported channels for the additional indoor module:** -| Channel ID | Item Type | Description | -|---------------------|----------------------|----------------------------------------------------------| -| Co2 | Number:Dimensionless | Air quality | -| MinCo2 | Number:Dimensionless | Minimum CO2 on current day | -| MinCo2ThisWeek | Number:Dimensionless | Minimum CO2 this week | -| MinCo2ThisMonth | Number:Dimensionless | Minimum CO2 this month | -| MaxCo2 | Number:Dimensionless | Maximum CO2 on current day | -| MaxCo2ThisWeek | Number:Dimensionless | Maximum CO2 this week | -| MaxCo2ThisMonth | Number:Dimensionless | Maximum CO2 this month | -| DateMinCo2 | DateTime | Date when minimum CO2 was reached on current day | -| DateMinCo2ThisWeek | DateTime | Date when minimum CO2 was reached this week | -| DateMinCo2ThisMonth | DateTime | Date when minimum CO2 was reached this month | -| DateMaxCo2 | DateTime | Date when maximum CO2 was reached on current day | -| DateMaxCo2ThisWeek | DateTime | Date when maximum CO2 was reached this week | -| DateMaxCo2ThisMonth | DateTime | Date when maximum CO2 was reached this month | -| Temperature | Number:Temperature | Current temperature | -| TempTrend | String | Temperature evolution trend (up, down, stable) | -| Humidity | Number:Dimensionless | Current humidity | -| MinHumidity | Number:Dimensionless | Minimum humidity on current day | -| MinHumidityThisWeek | Number:Dimensionless | Minimum humidity this week | -| MinHumidityThisMonth| Number:Dimensionless | Minimum humidity this month | -| MaxHumidity | Number:Dimensionless | Maximum humidity on current day | -| MaxHumidityThisWeek | Number:Dimensionless | Maximum humidity this week | -| MaxHumidityThisMonth| Number:Dimensionless | Maximum humidity this month | -| DateMinHumidity | DateTime | Date when minimum humidity was reached on current day | -| DateMinHumidityThisWeek | DateTime | Date when minimum humidity was reached this week | -| DateMinHumidityThisMonth| DateTime | Date when minimum humidity was reached this month | -| DateMaxHumidity | DateTime | Date when maximum humidity was reached on current day | -| DateMaxHumidityThisWeek | DateTime | Date when maximum humidity was reached this week | -| DateMaxHumidityThisMonth| DateTime | Date when maximum humidity was reached this month | -| Humidex | Number | Computed Humidex index | -| HeatIndex | Number:Temperature | Computed Heat Index | -| Dewpoint | Number:Temperature | Computed dewpoint temperature | -| DewpointDepression | Number:Temperature | Computed dewpoint depression | -| MinTemp | Number:Temperature | Minimum temperature on current day | -| MinTempThisWeek | Number:Temperature | Minimum temperature this week | -| MinTempThisMonth | Number:Temperature | Minimum temperature this month | -| MaxTemp | Number:Temperature | Maximum temperature on current day | -| MaxTempThisWeek | Number:Temperature | Maximum temperature this week | -| MaxTempThisMonth | Number:Temperature | Maximum temperature this month | -| DateMinTemp | DateTime | Date when minimum temperature was reached on current day | -| DateMinTempThisWeek | DateTime | Date when minimum temperature was reached this week | -| DateMinTempThisMonth| DateTime | Date when minimum temperature was reached this month | -| DateMaxTemp | DateTime | Date when maximum temperature was reached on current day | -| DateMaxTempThisWeek | DateTime | Date when maximum temperature was reached this week | -| DateMaxTempThisMonth| DateTime | Date when maximum temperature was reached this month | -| TimeStamp | DateTime | Timestamp when data was measured | -| LastMessage | DateTime | Last message emitted by the module | -| LowBattery | Switch | Low battery | -| BatteryVP | Number | Battery level | -| RfStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | +| Channel Group | Channel Id | Item Type | Description | +|---------------------|----------------------|----------------------|--------------------------------------------------| +| humidity | value | Number:Dimensionless | Current humidity | +| humidity | humidex | Number | Computed Humidex index | +| humidity | humidex-scale | Number | Humidex index appreciation | +| temperature | value | Number:Temperature | Current temperature | +| temperature | min-today | Number:Temperature | Minimum temperature on current day | +| temperature | max-today | Number:Temperature | Maximum temperature on current day | +| temperature | min-time | DateTime | Moment of today's minimum temperature | +| temperature | max-time | DateTime | Moment of today's maximum temperature | +| temperature | trend | String | Temperature evolution trend over time | +| temperature | heat-index | Number:Temperature | Computed Heat Index | +| temperature | dewpoint | Number:Temperature | Computed dewpoint temperature | +| temperature | dewpoint-depression | Number:Temperature | Computed dewpoint depression | +| airquality | co2 | Number:Dimensionless | Air quality | +| timestamp | last-seen | DateTime | Last time the module reported its presence | +| timestamp | measures | DateTime | Moment of the last measures update | +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | +| battery | value | Number | Battery level | +| battery | low-battery | Switch | Low battery | All these channels are read only. ### Rain Gauge -Example item for the **rain gauge** - -``` -Number Netatmo_Rain_Current "Rain [%.1f mm]" { channel = "netatmo:NAModule3:home:rain:Rain" } -``` **Supported channels for the rain guage:** -| Channel ID | Item Type | Description | -|---------------------|---------------|----------------------------------------------------------| -| Rain | Number:Length | Quantity of water | -| SumRain1 | Number:Length | Quantity of water on last hour | -| SumRain24 | Number:Length | Quantity of water on last day | -| SumRainThisWeek | Number:Length | Quantity of water this week | -| SumRainThisMonth | Number:Length | Quantity of water this month | -| TimeStamp | DateTime | Timestamp when data was measured | -| LastMessage | DateTime | Last message emitted by the module | -| LowBattery | Switch | Low battery | -| BatteryVP | Number | Battery level | -| RfStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | +| Channel Group | Channel Id | Item Type | Description | +|---------------------|----------------------|----------------------|--------------------------------------------------| +| rain | value | Number:Speed | Current precipitation intensity | +| rain | sum-1 | Number:Length | Quantity of water over last hour | +| rain | sum-24 | Number:Length | Quantity of water during the current day | +| timestamp | last-seen | DateTime | Last time the module reported its presence | +| timestamp | measures | DateTime | Moment of the last measures update | +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | +| battery | value | Number | Battery level | +| battery | low-battery | Switch | Low battery | All these channels are read only. ### Weather Station Wind module -Example item for the **wind module**: - -``` -Number Netatmo_Wind_Strength "Wind Strength [%.0f KPH]" { channel = "netatmo:NAModule2:home:wind:WindStrength" } -``` **Supported channels for the wind module:** -| Channel ID | Item Type | Description | -|---------------------|--------------|----------------------------------------------------------| -| WindAngle | Number:Angle | Current 5 minutes average wind direction | -| WindStrength | Number:Speed | Current 5 minutes average wind speed | -| GustAngle | Number:Angle | Direction of the last 5 minutes highest gust wind | -| GustStrength | Number:Speed | Speed of the last 5 minutes highest gust wind | -| TimeStamp | DateTime | Timestamp when data was measured | -| LastMessage | DateTime | Last message emitted by the module | -| LowBattery | Switch | Low battery | -| BatteryVP | Number | Battery level | -| RfStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | -| MaxWindStrength | Number:Speed | Maximum wind strength recorded | -| DateMaxWindStrength | DateTime | Timestamp when MaxWindStrength was recorded | +| Channel Group | Channel Id | Item Type | Description | +|---------------------|----------------------|----------------------|--------------------------------------------------| +| wind | angle | Number:Angle | Current 5 minutes average wind direction | +| wind | strength | Number:Speed | Current 5 minutes average wind speed | +| wind | max-strength | Number:Speed | Maximum wind strength recorded | +| wind | max-strength-date | DateTime | Moment when MaxWindStrength was recorded | +| wind | gust-angle | Number:Angle | Direction of the last 5 minutes highest gust | +| wind | gust-strength | Number:Speed | Speed of the last 5 minutes highest gust wind | +| timestamp | last-seen | DateTime | Last time the module reported its presence | +| timestamp | measures | DateTime | Moment of the last measures update | +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | +| battery | value | Number | Battery level | +| battery | low-battery | Switch | Low battery | All these channels are read only. ### Healthy Home Coach Device -Example item for the **Healthy Home Coach**: - -``` -String Netatmo_LivingRoom_HomeCoach_HealthIndex "Climate" { channel = "netatmo:NHC:home:livingroom:HealthIndex" } -``` **Supported channels for the healthy home coach device:** -| Channel ID | Item Type | Description | -|---------------------|----------------------|----------------------------------------------------------| -| HealthIndex | String | Health index (healthy, fine, fair, poor, unhealthy) | -| Co2 | Number:Dimensionless | Air quality | -| Temperature | Number:Temperature | Current temperature | -| TempTrend | String | Temperature evolution trend (up, down, stable) | -| Noise | Number:Dimensionless | Current noise level | -| Pressure | Number:Pressure | Current pressure | -| PressTrend | String | Pressure evolution trend for last 12h (up, down, stable) | -| AbsolutePressure | Number:Pressure | Absolute pressure | -| Humidity | Number:Dimensionless | Current humidity | -| MinTemp | Number:Temperature | Minimum temperature on current day | -| MaxTemp | Number:Temperature | Maximum temperature on current day | -| DateMinTemp | DateTime | Date when minimum temperature was reached on current day | -| DateMaxTemp | DateTime | Date when maximum temperature was reached on current day | -| TimeStamp | DateTime | Timestamp when data was measured | -| LastStatusStore | DateTime | Last status store | -| WifiStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | -| Location | Location | Location of the device | +| Channel Group | Channel Id | Item Type | Description | +|---------------------|----------------------|----------------------|--------------------------------------------------| +| noise | value | Number:Dimensionless | Current noise level | +| humidity | value | Number:Dimensionless | Current humidity | +| humidity | humidex | Number | Computed Humidex index | +| humidity | humidex-scale | Number | Humidex index appreciation | +| pressure | value | Number:Pressure | Current pressure | +| pressure | absolute | Number:Pressure | Pressure at sea level | +| temperature | value | Number:Temperature | Current temperature | +| temperature | min-today | Number:Temperature | Minimum temperature on current day | +| temperature | max-today | Number:Temperature | Maximum temperature on current day | +| temperature | min-time | DateTime | Moment of today's minimum temperature | +| temperature | max-time | DateTime | Moment of today's maximum temperature | +| temperature | heat-index | Number:Temperature | Computed Heat Index | +| temperature | dewpoint | Number:Temperature | Computed dewpoint temperature | +| temperature | dewpoint-depression | Number:Temperature | Computed dewpoint depression | +| airquality | health-index | Number | Health index (*) | +| airquality | co2 | Number:Dimensionless | Air quality | +| timestamp | last-seen | DateTime | Last time the module reported its presence | +| timestamp | measures | DateTime | Moment of the last measures update | +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | + +(*) Health index values : + +- 0 : healthy +- 1 : fine +- 2 : fair +- 3 : poor +- 4 : unhealthy All these channels are read only. @@ -421,38 +308,71 @@ All these channels are read only. **Supported channels for the thermostat relay device:** -| Channel ID | Item Type | Description | -|---------------------|-----------|----------------------------------------------------------| -| ConnectedBoiler | Switch | Plug connected boiler | -| LastPlugSeen | DateTime | Last plug seen | -| LastBilan | DateTime | Month of the last available thermostat bilan | -| LastStatusStore | DateTime | Last status store | -| WifiStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | -| Location | Location | Location of the device | +| Channel Group | Channel Id | Item Type | Description | +|---------------------|--------------------|----------------------|--------------------------------------------------| +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | All these channels are read only. +### Thermostat Plug + +**Supported channels for the thermostat plug device:** + +| Channel Group | Channel Id | Item Type | Description | +|---------------------|--------------------|----------------------|--------------------------------------------------| +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | + +All these channels are read only. + + +### Room + +**Supported channels for the Room thing:** + +| Channel Group | Channel Id | Item Type | Description | +|------------------|-----------------------|----------------------|---------------------------------------------------------| +| room-temperature | value | Number:Temperature | Current temperature in the room | +| room-properties | window-open | Contact | Windows of the room are opened | +| room-properties | anticipating | Switch | Anticipates next scheduled setpoint | +| room-properties | heating-power-request | Number:Dimensionless | Percentage of heating power | +| setpoint | value | Number:Temperature | Thermostat temperature setpoint | +| setpoint | mode | String | Chosen thermostat mode (home, frost guard, manual, max) | +| setpoint | start | DateTime | Start time of the currently applied setpoint | +| setpoint | end | DateTime | End time of the currently applied setpoint | + +All these channels except setpoint and setpoint-mode are read only. + + ### Thermostat Module **Supported channels for the thermostat module:** -| Channel ID | Item Type | Description | -|---------------------|--------------------|------------------------------------------------------------| -| Temperature | Number:Temperature | Current temperature | -| Sp_Temperature | Number:Temperature | Thermostat temperature setpoint | -| SetpointMode | String | Chosen setpoint_mode (program, away, hg, manual, off, max) | -| Planning | String | Id of the currently active planning when mode = program | -| ThermRelayCmd | Switch | Indicates whether the furnace is heating or not | -| ThermOrientation | Number | Physical orientation of the thermostat module | -| TimeStamp | DateTime | Timestamp when data was measured | -| SetpointEndTime | DateTime | Thermostat goes back to schedule after that timestamp | -| LastMessage | DateTime | Last message emitted by the module | -| LowBattery | Switch | Low battery | -| BatteryVP | Number | Battery level | -| RfStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) | +| Channel Group | Channel Id | Item Type | Description | +|---------------------|--------------------|----------------------|--------------------------------------------------| +| th-properties | relay-status | Contact | Indicates if the boiler is currently heating | +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | +| battery | value | Number | Battery level | +| battery | low-battery | Switch | Low battery | +| battery | status | String | Description of the battery status (*) | + +(*) Can be UNDEF on some modules + -All these channels except Sp_Temperature, SetpointMode and Planning are read only. +### Valve Module + +**Supported channels for the Valve module:** + +| Channel Group | Channel Id | Item Type | Description | +|---------------------|--------------------|----------------------|--------------------------------------------------| +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | +| battery | value | Number | Battery level | +| battery | low-battery | Switch | Low battery | +| battery | status | String | Description of the battery status (*) | ### Welcome Home @@ -461,54 +381,44 @@ All these channels are read only. **Supported channels for the Home thing:** -| Channel ID | Item Type | Description | -|--------------------------|-----------|----------------------------------------------------------| -| welcomeHomeCity | String | City of the home | -| welcomeHomeCountry | String | Country of the home | -| welcomeHomeTimezone | String | Timezone of the home | -| welcomeHomePersonCount | Number | Total number of Persons that are at home | -| welcomeHomeUnknownCount | Number | Count how many Unknown Persons are at home | -| welcomeEventType | String | Type of event | -| welcomeEventTime | DateTime | Time of occurrence of event | -| welcomeEventCameraId | String | Camera that detected the event | -| welcomeEventPersonId | String | Id of the person the event is about (if any) | -| welcomeEventSnapshot | Image | picture of the last event, if it applies | -| welcomeEventSnapshotURL | String | if the last event (depending upon event type) in the home lead a snapshot picture, the picture URL will be available here | -| welcomeEventVideoURL | String | if the last event (depending upon event type) in the home lead a snapshot picture, the corresponding video URL will be available here | -| welcomeEventVideoStatus | String | Status of the video (recording, deleted or available) | -| welcomeEventIsArrival | Switch | If person was considered "away" before being seen during this event | -| welcomeEventMessage | String | Message sent by Netatmo corresponding to given event | -| welcomeEventSubType | String | Sub-type of SD and Alim events | +| Channel Group | Channel Id | Item Type | Description | +|---------------------|------------------------|------------------|--------------------------------------------------| +| security | person-count | Number | Total number of persons that are at home | +| security | unknown-person-count | Number | Total number of unknown persons that are at home | +| security | unknown-person-picture | Image | Snapshot of unknown person that is at home | + +All these channels are read only. + **Supported trigger channels for the Home thing:** -| Channel Type ID | Options | Description | -|------------------|------------------------|-------------------------------------------------------| +| Channel Type ID | Options | Description | +|------------------|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | cameraEvent | | A camera event is triggered with a short delay but without requiring a webhook. The information of the event can get retrieved from the other "welcomeEvent" home thing channels | -| | HUMAN | Triggered when a human (or person) was detected | -| | ANIMAL | Triggered when an animal was detected | -| | MOVEMENT | Triggered when an unspecified movement was detected | -| | VEHICLE | Triggered when a vehicle was detected | -| welcomeHomeEvent | | A welcome home event is triggered directly via a configured webhook | -| | PERSON | Triggered when a concrete person was detected | -| | PERSON_AWAY | Triggered when a concrete person leaves | -| | MOVEMENT | Triggered when a movement was detected | -| | CONNECTION | Triggered when a camera connection gets created | -| | DISCONNECTION | Triggered when a camera connection got lost | -| | ON | Triggered when camera monitoring is switched on | -| | OFF | Triggered when camera monitoring is switched off | -| | BOOT | Triggered when a camera is booting | -| | SD | Triggered when a camera SD card status was changed | -| | ALIM | Triggered when a power supply status was changed | -| | NEW_MODULE | Triggered when a new module was discovered | -| | MODULE_CONNECT | Triggered when a module gets connected | -| | MODULE_DISCONNECT | Triggered when a module gets disconnected | -| | MODULE_LOW_BATTERY | Triggered when the battery of a module gets low | -| | MODULE_END_UPDATE | Triggered when a firmware update of a module is done | -| | TAG_BIG_MOVE | Triggered when a big movement of a tag was detected | -| | TAG_SMALL_MOVE | Triggered when a small movement of a tag was detected | -| | TAG_UNINSTALLED | Triggered when a tag gets uninstalled | -| | TAG_OPEN | Triggered when an open event of a tag was detected | +| | HUMAN | Triggered when a human (or person) was detected | +| | ANIMAL | Triggered when an animal was detected | +| | MOVEMENT | Triggered when an unspecified movement was detected | +| | VEHICLE | Triggered when a vehicle was detected | +| welcomeHomeEvent | | A welcome home event is triggered directly via a configured webhook | +| | PERSON | Triggered when a concrete person was detected | +| | PERSON_AWAY | Triggered when a concrete person leaves | +| | MOVEMENT | Triggered when a movement was detected | +| | CONNECTION | Triggered when a camera connection gets created | +| | DISCONNECTION | Triggered when a camera connection got lost | +| | ON | Triggered when camera monitoring is switched on | +| | OFF | Triggered when camera monitoring is switched off | +| | BOOT | Triggered when a camera is booting | +| | SD | Triggered when a camera SD card status was changed | +| | ALIM | Triggered when a power supply status was changed | +| | NEW_MODULE | Triggered when a new module was discovered | +| | MODULE_CONNECT | Triggered when a module gets connected | +| | MODULE_DISCONNECT | Triggered when a module gets disconnected | +| | MODULE_LOW_BATTERY | Triggered when the battery of a module gets low | +| | MODULE_END_UPDATE | Triggered when a firmware update of a module is done | +| | TAG_BIG_MOVE | Triggered when a big movement of a tag was detected | +| | TAG_SMALL_MOVE | Triggered when a small movement of a tag was detected | +| | TAG_UNINSTALLED | Triggered when a tag gets uninstalled | +| | TAG_OPEN | Triggered when an open event of a tag was detected | ### Welcome and Presence Camera @@ -519,33 +429,61 @@ Warnings: **Supported channels for the Welcome Camera thing:** -| Channel ID | Item Type | Read/Write | Description | -|-----------------------------|-----------|------------|--------------------------------------------------------------| -| welcomeCameraStatus | Switch | Read-write | State of the camera (video surveillance on/off) | -| welcomeCameraSdStatus | Switch | Read-only | State of the SD card | -| welcomeCameraAlimStatus | Switch | Read-only | State of the power connector | -| welcomeCameraIsLocal | Switch | Read-only | indicates whether the camera is on the same network than the openHAB Netatmo Binding | -| welcomeCameraLivePicture | Image | Read-only | Camera Live Snapshot | -| welcomeCameraLivePictureUrl | String | Read-only | Url of the live snapshot for this camera | -| welcomeCameraLiveStreamUrl | String | Read-only | Url of the live stream for this camera | +| Channel Group | Channel ID | Item Type | Read/Write | Description | +|----------------|----------------------|--------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| status | monitoring | Switch | Read-write | State of the camera (video surveillance on/off) | +| status | sd-card | String | Read-only | State of the SD card | +| status | alim | String | Read-only | State of the power connector | +| live | picture | Image | Read-only | Camera Live Snapshot | +| live | local-picture-url | String | Read-only | Local Url of the live snapshot for this camera | +| live | vpn-picture-url | String | Read-only | Url of the live snapshot for this camera through Netatmo VPN. | +| live | local-stream-url (*) | String | Read-only | Local Url of the live stream for this camera (accessible if openhab server and camera are located on the same lan. | +| live | vpn-stream-url (*) | String | Read-only | Url of the live stream for this camera through Netatmo VPN. | +| signal | strength | Number | Read-only | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Read-only | Signal strength in dBm | +| last-event | type | String | Read-only | Type of event | +| last-event | subtype | String | Read-only | Sub-type of event | +| last-event | time | DateTime | Read-only | Time of occurrence of event | +| last-event | message | String | Read-only | Message sent by Netatmo corresponding to given event | +| last-event | snapshot | Image | Read-only | picture of the last event, if it applies | +| last-event | snapshot-url | String | Read-only | If the last event (depending upon event type) in the home lead a snapshot picture, the picture URL will be available here | +| last-event | local-video-url | String | Read-only | If the last event (depending upon event type) in the home lead a snapshot picture, the corresponding local video URL will be available here | +| last-event | vpn-video-url | String | Read-only | If the last event (depending upon event type) in the home lead a snapshot picture, the corresponding VPN video URL will be available here | +| last-event | video-status | String | Read-only | Status of the video (recording, deleted or available) | +| last-event | person-id | String | Read-only | Id of the person the event is about (if any) | + +(*) This channel is configurable : low, poor, high. **Supported channels for the Presence Camera thing:** Warnings: -- The floodlight auto-mode (cameraFloodlightAutoMode) isn't updated it is changed by another application. Therefore the binding handles its own state of the auto-mode. This has the advantage that the user can define its own floodlight switch off behaviour. - -| Channel ID | Item Type | Read/Write | Description | -|-----------------------------|-----------|------------|--------------------------------------------------------------| -| cameraStatus | Switch | Read-write | State of the camera (video surveillance on/off) | -| cameraSdStatus | Switch | Read-only | State of the SD card | -| cameraAlimStatus | Switch | Read-only | State of the power connector | -| cameraIsLocal | Switch | Read-only | indicates whether the camera is on the same network than the openHAB Netatmo Binding | -| cameraLivePicture | Image | Read-only | Camera Live Snapshot | -| cameraLivePictureUrl | String | Read-only | Url of the live snapshot for this camera | -| cameraLiveStreamUrl | String | Read-only | Url of the live stream for this camera | -| cameraFloodlightAutoMode | Switch | Read-write | When set the floodlight gets switched to auto instead of off | -| cameraFloodlight | Switch | Read-write | Switch for the floodlight | +- The floodlight auto-mode (auto-mode) isn't updated it is changed by another application. Therefore the binding handles its own state of the auto-mode. This has the advantage that the user can define its own floodlight switch off behaviour. + +| Channel Group | Channel ID | Item Type | Read/Write | Description | +|----------------|----------------------|--------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| status | monitoring | Switch | Read-write | State of the camera (video surveillance on/off) | +| status | sd-card | String | Read-only | State of the SD card | +| status | alim | String | Read-only | State of the power connector | +| live | picture | Image | Read-only | Camera Live Snapshot | +| live | picture-url | String | Read-only | Url of the live snapshot for this camera | +| live | local-stream-url (*) | String | Read-only | Local Url of the live stream for this camera (accessible if openhab server and camera are located on the same lan. | +| live | vpn-stream-url (*) | String | Read-only | Url of the live stream for this camera through Netatmo VPN. | +| signal | strength | Number | Read-only | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Read-only | Signal strength in dBm | +| presence | floodlight | Switch | Read-write | Sets the floodlight to ON/OFF/AUTO | +| last-event | type | String | Read-only | Type of event | +| last-event | subtype | String | Read-only | Sub-type of event | +| last-event | time | DateTime | Read-only | Time of occurrence of event | +| last-event | message | String | Read-only | Message sent by Netatmo corresponding to given event | +| last-event | snapshot | Image | Read-only | picture of the last event, if it applies | +| last-event | snapshot-url | String | Read-only | if the last event (depending upon event type) in the home lead a snapshot picture, the picture URL will be available here | +| last-event | local-video-url | String | Read-only | If the last event (depending upon event type) in the home lead a snapshot picture, the corresponding local video URL will be available here | +| last-event | vpn-video-url | String | Read-only | If the last event (depending upon event type) in the home lead a snapshot picture, the corresponding VPN video URL will be available here | +| last-event | video-status | String | Read-only | Status of the video (recording, deleted or available) | +| last-event | person-id | String | Read-only | Id of the person the event is about (if any) | + +(*) This channel is configurable : low, poor, high. ### Welcome Person @@ -559,18 +497,21 @@ Person things are automatically created in discovery process for all known perso **Supported channels for the Person thing:** -| Channel ID | Item Type | Description | -|-------------------------------|-----------|--------------------------------------------------------| -| welcomePersonLastSeen | DateTime | Time when this person was last seen | -| welcomePersonAtHome | Switch | Indicates if this person is known to be at home or not | -| welcomePersonAvatarUrl | String | URL for the avatar of this person | -| welcomePersonAvatar | Image | Avatar of this person | -| welcomePersonLastEventMessage | String | Last event message from this person | -| welcomePersonLastEventTime | DateTime | Last event message time for this person | -| welcomePersonLastEventUrl | String | URL for the picture of the last event for this person | -| welcomePersonLastEvent | Image | Picture of the last event for this person | +| Channel Group | Channel ID | Item Type | Description | +|----------------|----------------|--------------|--------------------------------------------------------| +| person | avatar-url | String | URL for the avatar of this person | +| person | avatar | Image | Avatar of this person | +| person | at-home | Switch | Indicates if this person is known to be at home or not | +| person | last-seen | DateTime | Moment when this person was last seen | +| person-event | subtype | String | Sub-type of event | +| person-event | message | String | Last event message from this person | +| person-event | time | DateTime | Moment of the last event for this person | +| person-event | snapshot | Image | Picture of the last event for this person | +| person-event | snapshot-url | String | URL for the picture of the last event for this person | +| person-event | camera-id | String | ID of the camera that triggered the event | + +All these channels except at-home are read only. -All these channels except welcomePersonAtHome are read only. # Configuration Examples @@ -578,114 +519,72 @@ All these channels except welcomePersonAtHome are read only. ## things/netatmo.things ``` -// Bridge configuration: -Bridge netatmo:netatmoapi:home "Netatmo API" [ clientId="*********", clientSecret="**********", username = "mail@example.com", password = "******", readStation=true, readThermostat=false] { - // Thing configuration: - Thing NAMain inside "Netatmo Inside" [ id="aa:aa:aa:aa:aa:aa" ] - Thing NAModule1 outside "Netatmo Outside" [ id="bb:bb:bb:bb:bb:bb", parentId="aa:aa:aa:aa:aa:aa" ] - Thing NAModule3 rain "Netatmo Rain" [ id="cc:cc:cc:cc:cc:cc", parentId="aa:aa:aa:aa:aa:aa" ] +Bridge netatmo:account:home "Netatmo Account" [clientId="", clientSecret="", username="", password=""] { + Bridge weather-station inside "Inside Weather Station" [id="70:ee:aa:aa:aa:aa"] { + outdoor outside "Outside Module" [id="02:00:00:aa:aa:aa"] { + Channels: + Type hum-measurement : maxHumWeek [limit="MAX",period="1week"] + } + rain rainModule "Rain Module" [id="05:00:00:aa:aa:aa"] { + Channels: + Type sum_rain-measurement: rainThisWeek "Rain This Week" [period="1week"] + Type sum_rain-measurement: rainThisMonth "Rain This Month" [period="1month"] + } + } } ``` + +## Sample configuration of live-stream-url channels: + +``` + .... + Thing welcome camera "Caméra" [ id="xxxxxx" ] { + Channels: + Type live-stream-url : live#local-stream-url [ quality="high" ] + Type live-stream-url : live#vpn-stream-url [ quality="low" ] + } + ... +``` + + ## items/netatmo.items ``` # Indoor Module -Number:Temperature Indoor_Temp "Temperature [%.1f %unit%]" { 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: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: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: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: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:Temperature Indoor_Temp "Temperature [%.1f %unit%]" { channel = "netatmo:weather-station:home:inside:temperature#value" } +Number:Temperature Indoor_Min_Temp "Min Temperature Today [%.1f %unit%]" { channel = "netatmo:weather-station:home:inside:temperature#min-today" } +Number:Temperature Indoor_Max_Temp "Max Temperature Today [%.1f %unit%]" { channel = "netatmo:weather-station:home:inside:temperature#max-today" } +Number:Dimensionless Indoor_Humidity "Humidity [%d %unit%]" { channel = "netatmo:weather-station:home:inside:humidity#value" } +Number Indoor_Humidex "Humidex [%.0f]" { channel = "netatmo:weather-station:home:inside:humidity#humidex" } +Number:Temperature Indoor_HeatIndex "HeatIndex [%.1f %unit%]" { channel = "netatmo:weather-station:home:inside:temperature#heat-index" } +Number:Temperature Indoor_Dewpoint "Dewpoint [%.1f %unit%]" { channel = "netatmo:weather-station:home:inside:temperature#dewpoint" } +Number:Temperature Indoor_DewpointDepression "DewpointDepression [%.1f %unit%]" { channel = "netatmo:weather-station:home:inside:temperature#dewpoint-depression" } +Number:Dimensionless Indoor_Co2 "CO2 [%d %unit%]" { channel = "netatmo:weather-station:home:inside:airquality#co2" } +Number:Pressure Indoor_Pressure "Pressure [%.1f %unit%]" { channel = "netatmo:weather-station:home:inside:pressure#value" } +Number:Pressure Indoor_AbsolutePressure "AbsolutePressure [%.1f %unit%]" { channel = "netatmo:weather-station:home:inside:pressure#absolute" } +Number:Dimensionless Indoor_Noise "Noise [%d %unit%]" { channel = "netatmo:weather-station:home:inside:noise#value" } +Number Indoor_RadioStatus "RadioStatus [%s]" { channel = "netatmo:weather-station:home:inside:signal#strength" } +DateTime Indoor_TimeStamp "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:weather-station:home:inside:timestamp#measures" } +DateTime Indoor_LastSeen "LastSeen [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:weather-station:home:inside:timestamp#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_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" } -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:Temperature Outdoor_Temperature "Temperature [%.1f %unit%]" { channel = "netatmo:outdoor:home:inside:outside:temperature#value" } +String Outdoor_TempTrend "TempTrend [%s]" { channel = "netatmo:outdoor:home:inside:outside:temperature#trend" } +Number:Dimensionless Outdoor_Humidity "Humidity [%d %unit%]" { channel = "netatmo:outdoor:home:inside:outside:humidity#value" } +Number Outdoor_Humidex "Humidex [%.0f]" { channel = "netatmo:outdoor:home:inside:outside:humidity#humidex" } +Number:Temperature Outdoor_HeatIndex "heat-index [%.1f %unit%]" { channel = "netatmo:outdoor:home:inside:outside:temperature#heat-index" } +Number:Temperature Outdoor_Dewpoint "Dewpoint [%.1f %unit%]" { channel = "netatmo:outdoor:home:inside:outside:temperature#dewpoint" } +Number:Temperature Outdoor_DewpointDepression "DewpointDepression [%.1f %unit%]" { channel = "netatmo:outdoor:home:inside:outside:temperature#dewpoint-depression" } +Number Outdoor_RadioStatus "RfStatus [%.0f / 5]" { channel = "netatmo:outdoor:home:inside:outside:signal#strength" } +Switch Outdoor_LowBattery "LowBattery [%s]" { channel = "netatmo:outdoor:home:inside:outside:battery#low-battery" } +DateTime Outdoor_TimeStamp "Measures TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:outdoor:home:inside:outside:timestamp#measures" } +DateTime Outdoor_LastMessage "LastMessage [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" { channel = "netatmo:outdoor:home:inside:outside:timestamp#last-seen" } # 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:Speed Rain_Intensity "Rain Intensity [%.1f %unit%]" { channel = "netatmo:rain:home:inside:rainModule:rain#value"} +Number:Length Rain_Hour "Rain Last Hour [%.1f %unit%]" { channel = "netatmo:rain:home:inside:rainModule:rain#sum-1"} +Number:Length Rain_Today "Rain Today [%.1f %unit%]" { channel = "netatmo:rain:home:inside:rainModule:rain#sum-24"} ``` ## sitemaps/netatmo.sitemap @@ -695,80 +594,23 @@ sitemap netatmo label="Netatmo" { Frame label="Indoor" { Text item=Indoor_Temp Text item=Indoor_Min_Temp - Text item=Indoor_Min_Temp_This_Week - Text item=Indoor_Min_Temp_This_Month Text item=Indoor_Max_Temp - Text item=Indoor_Max_Temp_This_Week - Text item=Indoor_Max_Temp_This_Month Text item=Indoor_Min_Temp_TS - Text item=Indoor_Min_Temp_This_Week_TS - Text item=Indoor_Min_Temp_This_Month_TS Text item=Indoor_Max_Temp_TS - Text item=Indoor_Max_Temp_This_Week_TS - Text item=Indoor_Max_Temp_This_Month_TS Text item=Indoor_Humidity - Text item=Indoor_Min_Humidity - Text item=Indoor_Min_Humidity_This_Week - Text item=Indoor_Min_Humidity_This_Month - Text item=Indoor_Max_Humidity - Text item=Indoor_Max_Humidity_This_Week - Text item=Indoor_Max_Humidity_This_Month - Text item=Indoor_Min_Humidity_TS - Text item=Indoor_Min_Humidity_This_Week_TS - Text item=Indoor_Min_Humidity_This_Month_TS - Text item=Indoor_Max_Humidity_TS - Text item=Indoor_Max_Humidity_This_Week_TS - Text item=Indoor_Max_Humidity_This_Month_TS Text item=Indoor_Humidex valuecolor=[<20.1="green",<29.1="blue",<28.1="yellow",<45.1="orange",<54.1="red",>54.1="maroon"] Text item=Indoor_HeatIndex Text item=Indoor_Dewpoint Text item=Indoor_DewpointDepression Text item=Indoor_Co2 valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"] - Text item=Indoor_Min_Co2 valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"] - Text item=Indoor_Min_Co2_This_Week valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"] - Text item=Indoor_Min_Co2_This_Month valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"] - Text item=Indoor_Max_Co2 valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"] - Text item=Indoor_Max_Co2_This_Week valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"] - Text item=Indoor_Max_Co2_This_Month valuecolor=[<800="green",<1000="orange",<1400="red",>1399="maroon"] - Text item=Indoor_Min_Co2_TS - Text item=Indoor_Min_Co2_This_Week_TS - Text item=Indoor_Min_Co2_This_Month_TS - Text item=Indoor_Max_Co2_TS - Text item=Indoor_Max_Co2_This_Week_TS - Text item=Indoor_Max_Co2_This_Month_TS Text item=Indoor_Pressure - Text item=Indoor_Min_Pressure - Text item=Indoor_Min_Pressure_This_Week - Text item=Indoor_Min_Pressure_This_Month - Text item=Indoor_Max_Pressure - Text item=Indoor_Max_Pressure_This_Week - Text item=Indoor_Max_Pressure_This_Month - Text item=Indoor_Min_Pressure_TS - Text item=Indoor_Min_Pressure_This_Week_TS - Text item=Indoor_Min_Pressure_This_Month_TS - Text item=Indoor_Max_Pressure_TS - Text item=Indoor_Max_Pressure_This_Week_TS - Text item=Indoor_Max_Pressure_This_Month_TS Text item=Indoor_AbsolutePressure Text item=Indoor_Noise - Text item=Indoor_Min_Noise - Text item=Indoor_Min_Noise_This_Week - Text item=Indoor_Min_Noise_This_Month - Text item=Indoor_Max_Noise - Text item=Indoor_Max_Noise_This_Week - Text item=Indoor_Max_Noise_This_Month - Text item=Indoor_Min_Noise_TS - Text item=Indoor_Min_Noise_This_Week_TS - Text item=Indoor_Min_Noise_This_Month_TS - Text item=Indoor_Max_Noise_TS - Text item=Indoor_Max_Noise_This_Week_TS - Text item=Indoor_Max_Noise_This_Month_TS Text item=Indoor_WifiStatus Text item=Indoor_TimeStamp - Text item=Indoor_Location - Text item=Indoor_LastStatusStore + Text item=Indoor_LastSeen } - Frame label="Outdoor" { + Frame label="Outdoor" { Text item=Outdoor_Temperature Text item=Outdoor_TempTrend Text item=Outdoor_Humidity @@ -776,13 +618,14 @@ sitemap netatmo label="Netatmo" { Text item=Outdoor_HeatIndex Text item=Outdoor_Dewpoint Text item=Outdoor_DewpointDepression - Text item=Outdoor_RfStatus + Text item=Outdoor_RadioStatus Text item=Outdoor_LowBattery Text item=Outdoor_BatteryVP Text item=Outdoor_TimeStamp Text item=Outdoor_LastMessage } Frame label="Rain" { + Text item=Rain_Intensity Text item=Rain_Hour Text item=Rain_Today Text item=Rain_Week @@ -796,9 +639,7 @@ sitemap netatmo label="Netatmo" { # Sample data If you want to evaluate this binding but have not got a Netatmo station yourself -yet, you can add the Netatmo office in Paris to your account: - -https://www.netatmo.com/en-US/addguest/index/TIQ3797dtfOmgpqUcct3/70:ee:50:00:02:20 +yet, you can search on the web for a publicly shared weather station. # Icons @@ -837,3 +678,5 @@ The following icons are used by original Netatmo web app: - https://my.netatmo.com/images/my/app/wifi_medium.png - https://my.netatmo.com/images/my/app/wifi_high.png - https://my.netatmo.com/images/my/app/wifi_full.png + + diff --git a/bundles/org.openhab.binding.netatmo/pom.xml b/bundles/org.openhab.binding.netatmo/pom.xml index 0c0e870cf407d..b6f562b57c810 100644 --- a/bundles/org.openhab.binding.netatmo/pom.xml +++ b/bundles/org.openhab.binding.netatmo/pom.xml @@ -14,114 +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 - - - - - - - - com.github.jknack - handlebars - 4.3.0 - - - - - - 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 5fd5a1ec8b235..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * {@link APIUtils} provides util methods for the usage of the generated API classes. - * - * @author Sven Strohschein - Initial contribution - */ -@NonNullByDefault -public final class APIUtils { - - private APIUtils() { - } - - public static 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 97b6a1cbd9ea3..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; - -import javax.measure.Unit; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.io.net.http.HttpUtil; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.types.RawType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; - -/** - * This class holds various channel values conversion methods - * - * @author Gaël L'hopital - Initial contribution - * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules - * - */ -@NonNullByDefault -public class ChannelTypeUtils { - - public static State toStringType(@Nullable String value) { - return (value == null) ? UnDefType.NULL : new StringType(value); - } - - public static ZonedDateTime toZonedDateTime(Integer netatmoTS, ZoneId zoneId) { - Instant i = Instant.ofEpochSecond(netatmoTS); - return ZonedDateTime.ofInstant(i, zoneId); - } - - public static State toDateTimeType(@Nullable Float netatmoTS, ZoneId zoneId) { - return netatmoTS == null ? UnDefType.NULL : toDateTimeType(toZonedDateTime(netatmoTS.intValue(), zoneId)); - } - - public static State toDateTimeType(@Nullable Integer netatmoTS, ZoneId zoneId) { - return netatmoTS == null ? UnDefType.NULL : toDateTimeType(toZonedDateTime(netatmoTS, zoneId)); - } - - public static State toDateTimeType(@Nullable ZonedDateTime zonedDateTime) { - return (zonedDateTime == null) ? UnDefType.NULL : new DateTimeType(zonedDateTime); - } - - public static State toDecimalType(@Nullable Float value) { - return (value == null) ? UnDefType.NULL : toDecimalType(new BigDecimal(value)); - } - - public static State toDecimalType(@Nullable Integer value) { - return (value == null) ? UnDefType.NULL : toDecimalType(new BigDecimal(value)); - } - - public static State toDecimalType(@Nullable Double value) { - return (value == null) ? UnDefType.NULL : toDecimalType(new BigDecimal(value)); - } - - public static State toDecimalType(float value) { - return toDecimalType(new BigDecimal(value)); - } - - public static State toDecimalType(double value) { - return toDecimalType(new BigDecimal(value)); - } - - public static State toDecimalType(@Nullable BigDecimal decimal) { - return decimal == null ? UnDefType.NULL : new DecimalType(decimal.setScale(2, RoundingMode.HALF_UP)); - } - - public static State toDecimalType(@Nullable String textualDecimal) { - return textualDecimal == null ? UnDefType.NULL : new DecimalType(textualDecimal); - } - - public static State toOnOffType(@Nullable String yesno) { - return "on".equalsIgnoreCase(yesno) ? OnOffType.ON : OnOffType.OFF; - } - - public static State toOnOffType(@Nullable Integer value) { - return value != null ? (value == 1 ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF; - } - - public static State toOnOffType(@Nullable Boolean value) { - return value != null ? (value ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF; - } - - public static State toQuantityType(@Nullable Float value, Unit unit) { - return value == null ? UnDefType.NULL : toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(@Nullable Integer value, Unit unit) { - return value == null ? UnDefType.NULL : toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(@Nullable Double value, Unit unit) { - return value == null ? UnDefType.NULL : toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(float value, Unit unit) { - return toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(int value, Unit unit) { - return toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(double value, Unit unit) { - return toQuantityType(new BigDecimal(value), unit); - } - - public static State toQuantityType(@Nullable BigDecimal value, Unit unit) { - return value == null ? UnDefType.NULL : new QuantityType<>(value, unit); - } - - public static State toRawType(String pictureUrl) { - RawType picture = HttpUtil.downloadImage(pictureUrl); - return picture == null ? UnDefType.UNDEF : picture; - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java index 1a5dc1413f463..481cfc06d9104 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,127 @@ */ package org.openhab.binding.netatmo.internal; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent.EventTypeEnum; -import org.openhab.core.thing.ThingTypeUID; /** - * The {@link NetatmoBinding} class defines common constants, which are used + * The {@link NetatmoBindingConstants} class defines common constants, which are used * across the whole binding. * * @author Gaël L'hopital - Initial contribution - * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules * */ @NonNullByDefault public class NetatmoBindingConstants { - private static final String BINDING_ID = "netatmo"; - + public static final String BINDING_ID = "netatmo"; public static final String VENDOR = "Netatmo"; // Configuration keys public static final String EQUIPMENT_ID = "id"; - public static final String PARENT_ID = "parentId"; - public static final String REFRESH_INTERVAL = "refreshInterval"; - public static final String SETPOINT_DEFAULT_DURATION = "setpointDefaultDuration"; - - public static final String WEBHOOK_APP = "app_security"; - - // Scale for Weather Station /getmeasure - public static final String THIRTY_MINUTES = "30min"; - public static final String ONE_HOUR = "1hour"; - public static final String THREE_HOURS = "3hours"; - public static final String ONE_DAY = "1day"; - public static final String ONE_WEEK = "1week"; - public static final String ONE_MONTH = "1month"; - - // Type for Weather Station /getmeasure - public static final String DATE_MIN_CO2 = "date_min_co2"; - public static final String DATE_MAX_CO2 = "date_max_co2"; - public static final String DATE_MIN_HUM = "date_min_hum"; - public static final String DATE_MAX_HUM = "date_max_hum"; - public static final String DATE_MIN_NOISE = "date_min_noise"; - public static final String DATE_MAX_NOISE = "date_max_noise"; - public static final String DATE_MIN_PRESSURE = "date_min_pressure"; - public static final String DATE_MAX_PRESSURE = "date_max_pressure"; - public static final String DATE_MIN_TEMP = "date_min_temp"; - public static final String DATE_MAX_TEMP = "date_max_temp"; - public static final String MIN_CO2 = "min_co2"; - public static final String MAX_CO2 = "max_co2"; - public static final String MIN_HUM = "min_hum"; - public static final String MAX_HUM = "max_hum"; - public static final String MIN_NOISE = "min_noise"; - public static final String MAX_NOISE = "max_noise"; - public static final String MIN_PRESSURE = "min_pressure"; - public static final String MAX_PRESSURE = "max_pressure"; - public static final String MIN_TEMP = "min_temp"; - public static final String MAX_TEMP = "max_temp"; - public static final String SUM_RAIN = "sum_rain"; - - // List of Bridge Type UIDs - public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "netatmoapi"); - - // List of Weather Station Things Type UIDs - public static final ThingTypeUID MAIN_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAMain"); - public static final ThingTypeUID MODULE1_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule1"); - public static final ThingTypeUID MODULE2_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule2"); - public static final ThingTypeUID MODULE3_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule3"); - public static final ThingTypeUID MODULE4_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule4"); - - // Netatmo Health Coach - public static final ThingTypeUID HOMECOACH_THING_TYPE = new ThingTypeUID(BINDING_ID, "NHC"); - - // List of Thermostat Things Type UIDs - public static final ThingTypeUID PLUG_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAPlug"); - public static final ThingTypeUID THERM1_THING_TYPE = new ThingTypeUID(BINDING_ID, "NATherm1"); - - // List of Welcome Home Things Type UIDs - public static final ThingTypeUID WELCOME_HOME_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAWelcomeHome"); - public static final ThingTypeUID WELCOME_CAMERA_THING_TYPE = new ThingTypeUID(BINDING_ID, "NACamera"); - public static final ThingTypeUID WELCOME_PERSON_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAWelcomePerson"); - // Presence camera - public static final ThingTypeUID PRESENCE_CAMERA_THING_TYPE = new ThingTypeUID(BINDING_ID, "NOC"); - - // Weather Station Channel ids - public static final String CHANNEL_TEMPERATURE = "Temperature"; - public static final String CHANNEL_TEMP_TREND = "TempTrend"; - public static final String CHANNEL_HUMIDITY = "Humidity"; - public static final String CHANNEL_MAX_HUMIDITY = "MaxHumidity"; - public static final String CHANNEL_MAX_HUMIDITY_THIS_WEEK = "MaxHumidityThisWeek"; - public static final String CHANNEL_MAX_HUMIDITY_THIS_MONTH = "MaxHumidityThisMonth"; - public static final String CHANNEL_MIN_HUMIDITY = "MinHumidity"; - public static final String CHANNEL_MIN_HUMIDITY_THIS_WEEK = "MinHumidityThisWeek"; - public static final String CHANNEL_MIN_HUMIDITY_THIS_MONTH = "MinHumidityThisMonth"; - public static final String CHANNEL_HUMIDEX = "Humidex"; - public static final String CHANNEL_TIMEUTC = "TimeStamp"; - public static final String CHANNEL_DEWPOINT = "Dewpoint"; - public static final String CHANNEL_DEWPOINTDEP = "DewpointDepression"; - public static final String CHANNEL_HEATINDEX = "HeatIndex"; - public static final String CHANNEL_LAST_STATUS_STORE = "LastStatusStore"; - public static final String CHANNEL_LAST_MESSAGE = "LastMessage"; - public static final String CHANNEL_LOCATION = "Location"; - public static final String CHANNEL_DATE_MAX_CO2 = "DateMaxCo2"; - public static final String CHANNEL_DATE_MAX_CO2_THIS_WEEK = "DateMaxCo2ThisWeek"; - public static final String CHANNEL_DATE_MAX_CO2_THIS_MONTH = "DateMaxCo2ThisMonth"; - public static final String CHANNEL_DATE_MIN_CO2 = "DateMinCo2"; - public static final String CHANNEL_DATE_MIN_CO2_THIS_WEEK = "DateMinCo2ThisWeek"; - public static final String CHANNEL_DATE_MIN_CO2_THIS_MONTH = "DateMinCo2ThisMonth"; - public static final String CHANNEL_DATE_MAX_HUMIDITY = "DateMaxHumidity"; - public static final String CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK = "DateMaxHumidityThisWeek"; - public static final String CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH = "DateMaxHumidityThisMonth"; - public static final String CHANNEL_DATE_MIN_HUMIDITY = "DateMinHumidity"; - public static final String CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK = "DateMinHumidityThisWeek"; - public static final String CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH = "DateMinHumidityThisMonth"; - public static final String CHANNEL_DATE_MAX_NOISE = "DateMaxNoise"; - public static final String CHANNEL_DATE_MAX_NOISE_THIS_WEEK = "DateMaxNoiseThisWeek"; - public static final String CHANNEL_DATE_MAX_NOISE_THIS_MONTH = "DateMaxNoiseThisMonth"; - public static final String CHANNEL_DATE_MIN_NOISE = "DateMinNoise"; - public static final String CHANNEL_DATE_MIN_NOISE_THIS_WEEK = "DateMinNoiseThisWeek"; - public static final String CHANNEL_DATE_MIN_NOISE_THIS_MONTH = "DateMinNoiseThisMonth"; - public static final String CHANNEL_DATE_MAX_PRESSURE = "DateMaxPressure"; - public static final String CHANNEL_DATE_MAX_PRESSURE_THIS_WEEK = "DateMaxPressureThisWeek"; - public static final String CHANNEL_DATE_MAX_PRESSURE_THIS_MONTH = "DateMaxPressureThisMonth"; - public static final String CHANNEL_DATE_MIN_PRESSURE = "DateMinPressure"; - public static final String CHANNEL_DATE_MIN_PRESSURE_THIS_WEEK = "DateMinPressureThisWeek"; - public static final String CHANNEL_DATE_MIN_PRESSURE_THIS_MONTH = "DateMinPressureThisMonth"; - public static final String CHANNEL_DATE_MAX_TEMP = "DateMaxTemp"; - public static final String CHANNEL_DATE_MAX_TEMP_THIS_WEEK = "DateMaxTempThisWeek"; - public static final String CHANNEL_DATE_MAX_TEMP_THIS_MONTH = "DateMaxTempThisMonth"; - public static final String CHANNEL_DATE_MIN_TEMP = "DateMinTemp"; - public static final String CHANNEL_DATE_MIN_TEMP_THIS_WEEK = "DateMinTempThisWeek"; - public static final String CHANNEL_DATE_MIN_TEMP_THIS_MONTH = "DateMinTempThisMonth"; - public static final String CHANNEL_MAX_TEMP = "MaxTemp"; - public static final String CHANNEL_MAX_TEMP_THIS_WEEK = "MaxTempThisWeek"; - public static final String CHANNEL_MAX_TEMP_THIS_MONTH = "MaxTempThisMonth"; - public static final String CHANNEL_MIN_TEMP = "MinTemp"; - public static final String CHANNEL_MIN_TEMP_THIS_WEEK = "MinTempThisWeek"; - public static final String CHANNEL_MIN_TEMP_THIS_MONTH = "MinTempThisMonth"; - public static final String CHANNEL_ABSOLUTE_PRESSURE = "AbsolutePressure"; - public static final String CHANNEL_CO2 = "Co2"; - public static final String CHANNEL_MAX_CO2 = "MaxCo2"; - public static final String CHANNEL_MAX_CO2_THIS_WEEK = "MaxCo2ThisWeek"; - public static final String CHANNEL_MAX_CO2_THIS_MONTH = "MaxCo2ThisMonth"; - public static final String CHANNEL_MIN_CO2 = "MinCo2"; - public static final String CHANNEL_MIN_CO2_THIS_WEEK = "MinCo2ThisWeek"; - public static final String CHANNEL_MIN_CO2_THIS_MONTH = "MinCo2ThisMonth"; - public static final String CHANNEL_NOISE = "Noise"; - public static final String CHANNEL_MAX_NOISE = "MaxNoise"; - public static final String CHANNEL_MAX_NOISE_THIS_WEEK = "MaxNoiseThisWeek"; - public static final String CHANNEL_MAX_NOISE_THIS_MONTH = "MaxNoiseThisMonth"; - public static final String CHANNEL_MIN_NOISE = "MinNoise"; - public static final String CHANNEL_MIN_NOISE_THIS_WEEK = "MinNoiseThisWeek"; - public static final String CHANNEL_MIN_NOISE_THIS_MONTH = "MinNoiseThisMonth"; - public static final String CHANNEL_PRESSURE = "Pressure"; - public static final String CHANNEL_MAX_PRESSURE = "MaxPressure"; - public static final String CHANNEL_MAX_PRESSURE_THIS_WEEK = "MaxPressureThisWeek"; - public static final String CHANNEL_MAX_PRESSURE_THIS_MONTH = "MaxPressureThisMonth"; - public static final String CHANNEL_MIN_PRESSURE = "MinPressure"; - public static final String CHANNEL_MIN_PRESSURE_THIS_WEEK = "MinPressureThisWeek"; - public static final String CHANNEL_MIN_PRESSURE_THIS_MONTH = "MinPressureThisMonth"; - public static final String CHANNEL_PRESS_TREND = "PressTrend"; - public static final String CHANNEL_RAIN = "Rain"; - public static final String CHANNEL_SUM_RAIN1 = "SumRain1"; - public static final String CHANNEL_SUM_RAIN24 = "SumRain24"; - public static final String CHANNEL_SUM_RAIN_THIS_WEEK = "SumRainThisWeek"; - public static final String CHANNEL_SUM_RAIN_THIS_MONTH = "SumRainThisMonth"; - public static final String CHANNEL_WIND_ANGLE = "WindAngle"; - public static final String CHANNEL_WIND_STRENGTH = "WindStrength"; - public static final String CHANNEL_MAX_WIND_STRENGTH = "MaxWindStrength"; - public static final String CHANNEL_DATE_MAX_WIND_STRENGTH = "DateMaxWindStrength"; - public static final String CHANNEL_GUST_ANGLE = "GustAngle"; - public static final String CHANNEL_GUST_STRENGTH = "GustStrength"; - public static final String CHANNEL_LOW_BATTERY = "LowBattery"; - public static final String CHANNEL_BATTERY_LEVEL = "BatteryVP"; - public static final String CHANNEL_WIFI_STATUS = "WifiStatus"; - public static final String CHANNEL_RF_STATUS = "RfStatus"; - - // Healthy Home Coach specific channel - public static final String CHANNEL_HEALTH_INDEX = "HealthIndex"; - - // Thermostat specific channels - public static final String CHANNEL_SETPOINT_MODE = "SetpointMode"; - public static final String CHANNEL_SETPOINT_END_TIME = "SetpointEndTime"; - public static final String CHANNEL_SETPOINT_TEMP = "Sp_Temperature"; - public static final String CHANNEL_THERM_RELAY = "ThermRelayCmd"; - public static final String CHANNEL_THERM_ORIENTATION = "ThermOrientation"; - public static final String CHANNEL_CONNECTED_BOILER = "ConnectedBoiler"; - public static final String CHANNEL_LAST_PLUG_SEEN = "LastPlugSeen"; - public static final String CHANNEL_LAST_BILAN = "LastBilan"; - - public static final String CHANNEL_PLANNING = "Planning"; - - public static final String CHANNEL_SETPOINT_MODE_MANUAL = "manual"; - public static final String CHANNEL_SETPOINT_MODE_AWAY = "away"; - public static final String CHANNEL_SETPOINT_MODE_HG = "hg"; - public static final String CHANNEL_SETPOINT_MODE_OFF = "off"; - public static final String CHANNEL_SETPOINT_MODE_MAX = "max"; - public static final String CHANNEL_SETPOINT_MODE_PROGRAM = "program"; - - // Module Properties - public static final String PROPERTY_SIGNAL_LEVELS = "signalLevels"; - public static final String PROPERTY_BATTERY_LEVELS = "batteryLevels"; - public static final String PROPERTY_REFRESH_PERIOD = "refreshPeriod"; - - // Welcome Home specific channels - public static final String CHANNEL_WELCOME_HOME_CITY = "welcomeHomeCity"; - public static final String CHANNEL_WELCOME_HOME_COUNTRY = "welcomeHomeCountry"; - public static final String CHANNEL_WELCOME_HOME_TIMEZONE = "welcomeHomeTimezone"; - public static final String CHANNEL_WELCOME_HOME_PERSONCOUNT = "welcomeHomePersonCount"; - public static final String CHANNEL_WELCOME_HOME_UNKNOWNCOUNT = "welcomeHomeUnknownCount"; - - public static final String CHANNEL_WELCOME_HOME_EVENT = "welcomeHomeEvent"; - - public static final String CHANNEL_CAMERA_EVENT = "cameraEvent"; - - public static final String CHANNEL_WELCOME_PERSON_LASTSEEN = "welcomePersonLastSeen"; - public static final String CHANNEL_WELCOME_PERSON_ATHOME = "welcomePersonAtHome"; - public static final String CHANNEL_WELCOME_PERSON_AVATAR_URL = "welcomePersonAvatarUrl"; - public static final String CHANNEL_WELCOME_PERSON_AVATAR = "welcomePersonAvatar"; - public static final String CHANNEL_WELCOME_PERSON_LASTMESSAGE = "welcomePersonLastEventMessage"; - public static final String CHANNEL_WELCOME_PERSON_LASTTIME = "welcomePersonLastEventTime"; - public static final String CHANNEL_WELCOME_PERSON_LASTEVENT = "welcomePersonLastEvent"; - public static final String CHANNEL_WELCOME_PERSON_LASTEVENT_URL = "welcomePersonLastEventUrl"; - - public static final String CHANNEL_WELCOME_CAMERA_STATUS = "welcomeCameraStatus"; - public static final String CHANNEL_WELCOME_CAMERA_SDSTATUS = "welcomeCameraSdStatus"; - public static final String CHANNEL_WELCOME_CAMERA_ALIMSTATUS = "welcomeCameraAlimStatus"; - public static final String CHANNEL_WELCOME_CAMERA_ISLOCAL = "welcomeCameraIsLocal"; - public static final String CHANNEL_WELCOME_CAMERA_LIVEPICTURE = "welcomeCameraLivePicture"; - public static final String CHANNEL_WELCOME_CAMERA_LIVEPICTURE_URL = "welcomeCameraLivePictureUrl"; - public static final String CHANNEL_WELCOME_CAMERA_LIVESTREAM_URL = "welcomeCameraLiveStreamUrl"; - - public static final String CHANNEL_WELCOME_EVENT_TYPE = "welcomeEventType"; - public static final String CHANNEL_WELCOME_EVENT_TIME = "welcomeEventTime"; - public static final String CHANNEL_WELCOME_EVENT_CAMERAID = "welcomeEventCameraId"; - public static final String CHANNEL_WELCOME_EVENT_PERSONID = "welcomeEventPersonId"; - public static final String CHANNEL_WELCOME_EVENT_SNAPSHOT = "welcomeEventSnapshot"; - public static final String CHANNEL_WELCOME_EVENT_SNAPSHOT_URL = "welcomeEventSnapshotURL"; - public static final String CHANNEL_WELCOME_EVENT_VIDEO_URL = "welcomeEventVideoURL"; - public static final String CHANNEL_WELCOME_EVENT_VIDEOSTATUS = "welcomeEventVideoStatus"; - public static final String CHANNEL_WELCOME_EVENT_ISARRIVAL = "welcomeEventIsArrival"; - public static final String CHANNEL_WELCOME_EVENT_MESSAGE = "welcomeEventMessage"; - public static final String CHANNEL_WELCOME_EVENT_SUBTYPE = "welcomeEventSubType"; - - // Camera specific channels - public static final String CHANNEL_CAMERA_STATUS = "cameraStatus"; - public static final String CHANNEL_CAMERA_SDSTATUS = "cameraSdStatus"; - public static final String CHANNEL_CAMERA_ALIMSTATUS = "cameraAlimStatus"; - public static final String CHANNEL_CAMERA_ISLOCAL = "cameraIsLocal"; - public static final String CHANNEL_CAMERA_LIVEPICTURE = "cameraLivePicture"; - public static final String CHANNEL_CAMERA_LIVEPICTURE_URL = "cameraLivePictureUrl"; - public static final String CHANNEL_CAMERA_LIVESTREAM_URL = "cameraLiveStreamUrl"; - - public static final String WELCOME_PICTURE_URL = "https://api.netatmo.com/api/getcamerapicture"; - public static final String WELCOME_PICTURE_IMAGEID = "image_id"; - public static final String WELCOME_PICTURE_KEY = "key"; - - // Presence outdoor camera specific channels - public static final String CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE = "cameraFloodlightAutoMode"; - public static final String CHANNEL_CAMERA_FLOODLIGHT = "cameraFloodlight"; - - // List of all supported physical devices and modules - public static final Set 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 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()); + // Things properties + public static final String PROPERTY_CITY = "city"; + public static final String PROPERTY_COUNTRY = "country"; + public static final String PROPERTY_TIMEZONE = "timezone"; + public static final String PROPERTY_FEATURE = "feature"; + + // Channel group ids + public static final String GROUP_LAST_EVENT = "last-event"; + public static final String GROUP_TEMPERATURE = "temperature"; + public static final String GROUP_HUMIDITY = "humidity"; + public static final String GROUP_AIR_QUALITY = "airquality"; + public static final String GROUP_NOISE = "noise"; + public static final String GROUP_PRESSURE = "pressure"; + public static final String GROUP_TIMESTAMP = "timestamp"; + public static final String GROUP_RAIN = "rain"; + public static final String GROUP_WIND = "wind"; + public static final String GROUP_ENERGY = "energy"; + public static final String GROUP_SIGNAL = "signal"; + public static final String GROUP_BATTERY = "battery"; + public static final String GROUP_SECURITY = "security"; + public static final String GROUP_CAM_STATUS = "status"; + public static final String GROUP_CAM_LIVE = "live"; + public static final String GROUP_PRESENCE = "presence"; + public static final String GROUP_PERSON = "person"; + public static final String GROUP_PERSON_EVENT = "person-event"; + public static final String GROUP_ROOM_TEMPERATURE = "room-temperature"; + public static final String GROUP_ROOM_PROPERTIES = "room-properties"; + public static final String GROUP_TH_PROPERTIES = "th-properties"; + public static final String GROUP_TH_SETPOINT = "setpoint"; + public static final String GROUP_LOCATION = "location"; + + // Alternative extended groups + public static final String OPTION_EXTENDED = "-extended"; + public static final String OPTION_OUTSIDE = "-outside"; + public static final String GROUP_TYPE_TIMESTAMP_EXTENDED = GROUP_TIMESTAMP + OPTION_EXTENDED; + public static final String GROUP_TYPE_BATTERY_EXTENDED = GROUP_BATTERY + OPTION_EXTENDED; + public static final String GROUP_TYPE_PRESSURE_EXTENDED = GROUP_PRESSURE + OPTION_EXTENDED; + public static final String GROUP_TYPE_TEMPERATURE_EXTENDED = GROUP_TEMPERATURE + OPTION_EXTENDED; + public static final String GROUP_TYPE_AIR_QUALITY_EXTENDED = GROUP_AIR_QUALITY + OPTION_EXTENDED; + public static final String GROUP_TYPE_TEMPERATURE_OUTSIDE = GROUP_TEMPERATURE + OPTION_OUTSIDE; + + // Channel ids + public static final String CHANNEL_VALUE = "value"; + public static final String CHANNEL_TREND = "trend"; + public static final String CHANNEL_MAX_TIME = "max-time"; + public static final String CHANNEL_MIN_TIME = "min-time"; + public static final String CHANNEL_MAX_VALUE = "max-today"; + public static final String CHANNEL_MIN_VALUE = "min-today"; + public static final String CHANNEL_HUMIDEX = "humidex"; + public static final String CHANNEL_CO2 = "co2"; + public static final String CHANNEL_HEALTH_INDEX = "health-index"; + public static final String CHANNEL_HUMIDEX_SCALE = "humidex-scale"; + public static final String CHANNEL_DEWPOINT = "dewpoint"; + public static final String CHANNEL_DEWPOINT_DEP = "dewpoint-depression"; + public static final String CHANNEL_HEAT_INDEX = "heat-index"; + public static final String CHANNEL_ABSOLUTE_PRESSURE = "absolute"; + public static final String CHANNEL_LAST_SEEN = "last-seen"; + public static final String CHANNEL_MEASURES_TIMESTAMP = "measures"; + public static final String CHANNEL_LOW_BATTERY = "low-battery"; + public static final String CHANNEL_BATTERY_STATUS = "status"; + public static final String CHANNEL_SIGNAL_STRENGTH = "strength"; + public static final String CHANNEL_SUM_RAIN1 = "sum-1"; + public static final String CHANNEL_SUM_RAIN24 = "sum-24"; + public static final String CHANNEL_WIND_ANGLE = "angle"; + public static final String CHANNEL_WIND_STRENGTH = "strength"; + public static final String CHANNEL_MAX_WIND_STRENGTH = "max-strength"; + public static final String CHANNEL_DATE_MAX_WIND_STRENGTH = "max-strength-date"; + public static final String CHANNEL_GUST_ANGLE = "gust-angle"; + public static final String CHANNEL_GUST_STRENGTH = "gust-strength"; + public static final String CHANNEL_SETPOINT_MODE = "mode"; + public static final String CHANNEL_SETPOINT_START_TIME = "start"; + public static final String CHANNEL_SETPOINT_END_TIME = "end"; + public static final String CHANNEL_THERM_RELAY = "relay-status"; + public static final String CHANNEL_ANTICIPATING = "anticipating"; + public static final String CHANNEL_ROOM_WINDOW_OPEN = "window-open"; + public static final String CHANNEL_ROOM_HEATING_POWER = "heating-power-request"; + public static final String CHANNEL_PLANNING = "planning"; + public static final String CHANNEL_PERSON_COUNT = "person-count"; + public static final String CHANNEL_UNKNOWN_PERSON_COUNT = "unknown-person-count"; + public static final String CHANNEL_UNKNOWN_PERSON_PICTURE = "unknown-person-picture"; + public static final String CHANNEL_MONITORING = "monitoring"; + public static final String CHANNEL_SD_CARD = "sd-card"; + public static final String CHANNEL_ALIM_STATUS = "alim"; + public static final String CHANNEL_LIVEPICTURE = "picture"; + public static final String CHANNEL_LIVEPICTURE_VPN_URL = "vpn-picture-url"; + public static final String CHANNEL_LIVEPICTURE_LOCAL_URL = "local-picture-url"; + public static final String CHANNEL_LIVESTREAM_VPN_URL = "vpn-stream-url"; + public static final String CHANNEL_LIVESTREAM_LOCAL_URL = "local-stream-url"; + public static final String CHANNEL_EVENT_TYPE = "type"; + public static final String CHANNEL_EVENT_SUBTYPE = "subtype"; + public static final String CHANNEL_EVENT_VIDEO_STATUS = "video-status"; + public static final String CHANNEL_EVENT_MESSAGE = "message"; + public static final String CHANNEL_EVENT_TIME = "time"; + public static final String CHANNEL_EVENT_SNAPSHOT = "snapshot"; + public static final String CHANNEL_EVENT_SNAPSHOT_URL = "snapshot-url"; + public static final String CHANNEL_EVENT_VIDEO_VPN_URL = "vpn-video-url"; + public static final String CHANNEL_EVENT_VIDEO_LOCAL_URL = "local-video-url"; + public static final String CHANNEL_EVENT_PERSON_ID = "person-id"; + public static final String CHANNEL_EVENT_CAMERA_ID = "camera-id"; + public static final String CHANNEL_PERSON_AT_HOME = "at-home"; + public static final String CHANNEL_PERSON_AVATAR = "avatar"; + public static final String CHANNEL_PERSON_AVATAR_URL = "avatar-url"; + public static final String CHANNEL_HOME_EVENT = "home-event"; + public static final String CHANNEL_SETPOINT_DURATION = "setpoint-duration"; + public static final String CHANNEL_FLOODLIGHT = "floodlight"; } 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 9df2de1e3e69d..0ab898bbc95d9 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,47 +12,46 @@ */ package org.openhab.binding.netatmo.internal; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Hashtable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -import javax.servlet.http.HttpServlet; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.discovery.NetatmoModuleDiscoveryService; -import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler; -import org.openhab.binding.netatmo.internal.homecoach.NAHealthyHomeCoachHandler; -import org.openhab.binding.netatmo.internal.presence.NAPresenceCameraHandler; -import org.openhab.binding.netatmo.internal.station.NAMainHandler; -import org.openhab.binding.netatmo.internal.station.NAModule1Handler; -import org.openhab.binding.netatmo.internal.station.NAModule2Handler; -import org.openhab.binding.netatmo.internal.station.NAModule3Handler; -import org.openhab.binding.netatmo.internal.station.NAModule4Handler; -import org.openhab.binding.netatmo.internal.thermostat.NAPlugHandler; -import org.openhab.binding.netatmo.internal.thermostat.NATherm1Handler; -import org.openhab.binding.netatmo.internal.webhook.WelcomeWebHookServlet; -import org.openhab.binding.netatmo.internal.welcome.NAWelcomeCameraHandler; -import org.openhab.binding.netatmo.internal.welcome.NAWelcomeHomeHandler; -import org.openhab.binding.netatmo.internal.welcome.NAWelcomePersonHandler; -import org.openhab.core.config.discovery.DiscoveryService; -import org.openhab.core.i18n.LocaleProvider; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.i18n.TranslationProvider; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.binding.netatmo.internal.config.BindingConfiguration; +import org.openhab.binding.netatmo.internal.deserialization.NADeserializer; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.handler.DeviceHandler; +import org.openhab.binding.netatmo.internal.handler.ModuleHandler; +import org.openhab.binding.netatmo.internal.handler.capability.AirCareCapability; +import org.openhab.binding.netatmo.internal.handler.capability.CameraCapability; +import org.openhab.binding.netatmo.internal.handler.capability.Capability; +import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability; +import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability; +import org.openhab.binding.netatmo.internal.handler.capability.EventCapability; +import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability; +import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability; +import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability; +import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability; +import org.openhab.binding.netatmo.internal.handler.capability.RoomCapability; +import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability; +import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; +import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; +import org.openhab.core.config.core.ConfigParser; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; -import org.osgi.framework.ServiceRegistration; -import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; import org.osgi.service.http.HttpService; import org.slf4j.Logger; @@ -68,131 +67,91 @@ @Component(service = ThingHandlerFactory.class, configurationPid = "binding.netatmo") 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 NetatmoDescriptionProvider stateDescriptionProvider; + private final HttpClient httpClient; + private final NADeserializer deserializer; private final HttpService httpService; - private final NATherm1StateDescriptionProvider stateDescriptionProvider; - private final TimeZoneProvider timeZoneProvider; - private final LocaleProvider localeProvider; - private final TranslationProvider translationProvider; - private boolean backgroundDiscovery; + private final BindingConfiguration configuration = new BindingConfiguration(); @Activate - public NetatmoHandlerFactory(final @Reference HttpService httpService, - final @Reference NATherm1StateDescriptionProvider stateDescriptionProvider, - final @Reference TimeZoneProvider timeZoneProvider, final @Reference LocaleProvider localeProvider, - final @Reference TranslationProvider translationProvider) { - this.httpService = httpService; + public NetatmoHandlerFactory(@Reference NetatmoDescriptionProvider stateDescriptionProvider, + @Reference HttpClientFactory factory, @Reference NADeserializer deserializer, + @Reference HttpService httpService, Map config) { this.stateDescriptionProvider = stateDescriptionProvider; - this.timeZoneProvider = timeZoneProvider; - this.localeProvider = localeProvider; - this.translationProvider = translationProvider; + this.httpClient = factory.getCommonHttpClient(); + this.httpService = httpService; + this.deserializer = deserializer; + configChanged(config); } - @Override - protected void activate(ComponentContext componentContext) { - super.activate(componentContext); - Dictionary properties = componentContext.getProperties(); - Object property = properties.get("backgroundDiscovery"); - if (property instanceof Boolean) { - backgroundDiscovery = ((Boolean) property).booleanValue(); - } else { - backgroundDiscovery = false; + @Modified + public void configChanged(Map config) { + BindingConfiguration newConf = ConfigParser.configurationAs(config, BindingConfiguration.class); + if (newConf != null) { + configuration.update(newConf); } - logger.debug("backgroundDiscovery {}", backgroundDiscovery); } @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)); + return ModuleType.AS_SET.stream().anyMatch(mt -> mt.thingTypeUID.equals(thingTypeUID)); } @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (thingTypeUID.equals(APIBRIDGE_THING_TYPE)) { - WelcomeWebHookServlet servlet = registerWebHookServlet(thing.getUID()); - NetatmoBridgeHandler bridgeHandler = new NetatmoBridgeHandler((Bridge) thing, servlet); - registerDeviceDiscoveryService(bridgeHandler); - return bridgeHandler; - } else if (thingTypeUID.equals(MODULE1_THING_TYPE)) { - return new NAModule1Handler(thing, timeZoneProvider); - } else if (thingTypeUID.equals(MODULE2_THING_TYPE)) { - return new NAModule2Handler(thing, timeZoneProvider); - } else if (thingTypeUID.equals(MODULE3_THING_TYPE)) { - return new NAModule3Handler(thing, timeZoneProvider); - } else if (thingTypeUID.equals(MODULE4_THING_TYPE)) { - return new NAModule4Handler(thing, timeZoneProvider); - } else if (thingTypeUID.equals(MAIN_THING_TYPE)) { - return new NAMainHandler(thing, timeZoneProvider); - } else if (thingTypeUID.equals(HOMECOACH_THING_TYPE)) { - return new NAHealthyHomeCoachHandler(thing, timeZoneProvider); - } else if (thingTypeUID.equals(PLUG_THING_TYPE)) { - return new NAPlugHandler(thing, timeZoneProvider); - } else if (thingTypeUID.equals(THERM1_THING_TYPE)) { - return new NATherm1Handler(thing, stateDescriptionProvider, timeZoneProvider); - } else if (thingTypeUID.equals(WELCOME_HOME_THING_TYPE)) { - return new NAWelcomeHomeHandler(thing, timeZoneProvider); - } else if (thingTypeUID.equals(WELCOME_CAMERA_THING_TYPE)) { - return new NAWelcomeCameraHandler(thing, timeZoneProvider); - } else if (thingTypeUID.equals(PRESENCE_CAMERA_THING_TYPE)) { - return new NAPresenceCameraHandler(thing, timeZoneProvider); - } else if (thingTypeUID.equals(WELCOME_PERSON_THING_TYPE)) { - return new NAWelcomePersonHandler(thing, timeZoneProvider); - } else { - logger.warn("ThingHandler not found for {}", thing.getThingTypeUID()); - return null; - } + return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst() + .map(mt -> buildHandler(thing, mt)).orElse(null); } - @Override - protected void removeHandler(ThingHandler thingHandler) { - if (thingHandler instanceof NetatmoBridgeHandler) { - ThingUID thingUID = thingHandler.getThing().getUID(); - unregisterDeviceDiscoveryService(thingUID); - unregisterWebHookServlet(thingUID); + private BaseThingHandler buildHandler(Thing thing, ModuleType moduleType) { + if (ModuleType.ACCOUNT.equals(moduleType)) { + return new ApiBridgeHandler((Bridge) thing, httpClient, httpService, deserializer, configuration); } - } + CommonInterface handler = moduleType.isABridge() ? new DeviceHandler((Bridge) thing) : new ModuleHandler(thing); - private synchronized void registerDeviceDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler) { - if (bundleContext != null) { - NetatmoModuleDiscoveryService discoveryService = new NetatmoModuleDiscoveryService(netatmoBridgeHandler, - localeProvider, translationProvider); - Map configProperties = new HashMap<>(); - configProperties.put(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, - Boolean.valueOf(backgroundDiscovery)); - discoveryService.activate(configProperties); - discoveryServiceRegs.put(netatmoBridgeHandler.getThing().getUID(), bundleContext - .registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); - } - } - - private synchronized void unregisterDeviceDiscoveryService(ThingUID thingUID) { - ServiceRegistration serviceReg = discoveryServiceRegs.remove(thingUID); - if (serviceReg != null) { - NetatmoModuleDiscoveryService service = (NetatmoModuleDiscoveryService) bundleContext - .getService(serviceReg.getReference()); - serviceReg.unregister(); - if (service != null) { - service.deactivate(); + List helpers = new ArrayList<>(); + moduleType.channelHelpers.forEach(helperClass -> { + try { + helpers.add(helperClass.getConstructor().newInstance()); + } catch (ReflectiveOperationException e) { + logger.warn("Error creating or initializing helper class : {}", e.getMessage()); } - } - } + }); - private synchronized @Nullable WelcomeWebHookServlet registerWebHookServlet(ThingUID thingUID) { - WelcomeWebHookServlet servlet = null; - if (bundleContext != null) { - servlet = new WelcomeWebHookServlet(httpService, thingUID.getId()); - webHookServiceRegs.put(thingUID, - bundleContext.registerService(HttpServlet.class.getName(), servlet, new Hashtable<>())); - } - return servlet; - } + moduleType.capabilities.forEach(capability -> { + Capability newCap = null; + if (capability == DeviceCapability.class) { + newCap = new DeviceCapability(handler); + } else if (capability == AirCareCapability.class) { + newCap = new AirCareCapability(handler); + } else if (capability == EventCapability.class) { + newCap = new EventCapability(handler); + } else if (capability == HomeCapability.class) { + newCap = new HomeCapability(handler, stateDescriptionProvider); + } else if (capability == WeatherCapability.class) { + newCap = new WeatherCapability(handler); + } else if (capability == RoomCapability.class) { + newCap = new RoomCapability(handler); + } else if (capability == PersonCapability.class) { + newCap = new PersonCapability(handler, stateDescriptionProvider, helpers); + } else if (capability == CameraCapability.class) { + newCap = new CameraCapability(handler, stateDescriptionProvider, helpers); + } else if (capability == PresenceCapability.class) { + newCap = new PresenceCapability(handler, stateDescriptionProvider, helpers); + } else if (capability == MeasureCapability.class) { + newCap = new MeasureCapability(handler, helpers); + } else if (capability == ChannelHelperCapability.class) { + newCap = new ChannelHelperCapability(handler, helpers); + } + if (newCap != null) { + handler.getCapabilities().put(newCap); + } else { + logger.warn("No factory entry defined to create Capability : {}", capability); + } + }); - private synchronized void unregisterWebHookServlet(ThingUID thingUID) { - ServiceRegistration serviceReg = webHookServiceRegs.remove(thingUID); - if (serviceReg != null) { - serviceReg.unregister(); - } + return (BaseThingHandler) handler; } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/RefreshStrategy.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/RefreshStrategy.java deleted file mode 100644 index 5cff4349806b9..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/RefreshStrategy.java +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright (c) 2010-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal; - -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; -import java.util.Calendar; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * {@link RefreshStrategy} is the class used to embed the refreshing - * needs calculation for devices - * - * @author Gaël L'hopital - Initial contribution - * - */ -@NonNullByDefault -public class RefreshStrategy { - - private Logger logger = LoggerFactory.getLogger(RefreshStrategy.class); - - private static final int DEFAULT_DELAY = 30; // in seconds - private static final int SEARCH_REFRESH_INTERVAL = 120; // in seconds - private int dataValidityPeriod; - private long dataTimeStamp; - private boolean searchRefreshInterval; - @Nullable - private Integer dataTimestamp0; - - // By default we create dataTimeStamp to be outdated - // A null or negative value for dataValidityPeriod will trigger an automatic search of the validity period - public RefreshStrategy(int dataValidityPeriod) { - if (dataValidityPeriod <= 0) { - this.dataValidityPeriod = 0; - this.searchRefreshInterval = true; - logger.debug("Data validity period search..."); - } else { - this.dataValidityPeriod = dataValidityPeriod; - this.searchRefreshInterval = false; - logger.debug("Data validity period set to {} ms", this.dataValidityPeriod); - } - expireData(); - } - - @SuppressWarnings("null") - public void setDataTimeStamp(Integer dataTimestamp, ZoneId zoneId) { - if (searchRefreshInterval) { - if (dataTimestamp0 == null) { - dataTimestamp0 = dataTimestamp; - logger.debug("First data timestamp is {}", dataTimestamp0); - } else if (dataTimestamp.intValue() > dataTimestamp0.intValue()) { - dataValidityPeriod = (dataTimestamp.intValue() - dataTimestamp0.intValue()) * 1000; - searchRefreshInterval = false; - logger.debug("Data validity period found : {} ms", this.dataValidityPeriod); - } else { - logger.debug("Data validity period not yet found - data timestamp unchanged"); - } - } - this.dataTimeStamp = ChannelTypeUtils.toZonedDateTime(dataTimestamp, zoneId).toInstant().toEpochMilli(); - } - - public long dataAge() { - long now = Calendar.getInstance().getTimeInMillis(); - return now - dataTimeStamp; - } - - public boolean isDataOutdated() { - return dataAge() >= dataValidityPeriod; - } - - public long nextRunDelayInS() { - return searchRefreshInterval ? SEARCH_REFRESH_INTERVAL - : Math.max(0, (dataValidityPeriod - dataAge())) / 1000 + DEFAULT_DELAY; - } - - public void expireData() { - ZonedDateTime now = ZonedDateTime.now().minus(this.dataValidityPeriod, ChronoUnit.MILLIS); - dataTimeStamp = now.toInstant().toEpochMilli(); - } - - public boolean isSearchingRefreshInterval() { - return searchRefreshInterval && dataTimestamp0 != null; - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/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..192735630eba1 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/action/RoomActions.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.action; + +import java.util.Optional; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.handler.capability.EnergyCapability; +import org.openhab.core.automation.annotation.ActionInput; +import org.openhab.core.automation.annotation.RuleAction; +import org.openhab.core.thing.binding.ThingActions; +import org.openhab.core.thing.binding.ThingActionsScope; +import org.openhab.core.thing.binding.ThingHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link RoomActions} defines thing actions for RoomHandler. + * + * @author Markus Dillmann - Initial contribution + */ +@ThingActionsScope(name = "netatmo") +@NonNullByDefault +public class RoomActions implements ThingActions { + private final Logger logger = LoggerFactory.getLogger(RoomActions.class); + private static final Set ALLOWED_MODES = Set.of(SetpointMode.MAX, SetpointMode.MANUAL, + SetpointMode.HOME); + + private @Nullable CommonInterface handler; + private Optional energy = Optional.empty(); + + public RoomActions() { + logger.debug("Netatmo RoomActions service created"); + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + if (handler instanceof CommonInterface) { + CommonInterface commonHandler = (CommonInterface) handler; + this.handler = commonHandler; + energy = commonHandler.getHomeCapability(EnergyCapability.class); + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return (ThingHandler) handler; + } + + /** + * The setThermpoint room thing action + */ + @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc") + public void setThermpoint( + @ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp, + @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) { + setThermpoint(temp, endTime, "MANUAL"); + } + + @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc") + public void seThermpoint( + @ActionInput(name = "mode", label = "@text/actionInputModeLabel", description = "@text/actionInputModeDesc") @Nullable String mode, + @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) { + setThermpoint(null, endTime, mode); + } + + @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc") + public void setThermpoint( + @ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp, + @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime, + @ActionInput(name = "mode", label = "@text/actionInputModeLabel", description = "@text/actionInputModeDesc") @Nullable String mode) { + CommonInterface roomHandler = handler; + if (roomHandler != null) { + String roomId = roomHandler.getId(); + SetpointMode targetMode = SetpointMode.UNKNOWN; + Long targetEndTime = endTime; + Double targetTemp = temp; + if (mode != null) { + try { + targetMode = SetpointMode.valueOf(mode); + if (!ALLOWED_MODES.contains(targetMode)) { + logger.info("Mode can only be MAX, HOME or MANUAL for a room"); + return; + } + } catch (IllegalArgumentException e) { + logger.info("Invalid mode passed : {} - {}", mode, e.getMessage()); + return; + } + } + if (temp != null) { + logger.debug("Temperature provided, mode forced to MANUAL."); + targetMode = SetpointMode.MANUAL; + if (targetEndTime == null) { + logger.info("Temperature provided but no endtime given, action ignored"); + return; + } + } else { + if (SetpointMode.HOME.equals(targetMode)) { + targetEndTime = 0L; + targetTemp = 0.0; + } else { + logger.info("mode is required if no temperature setpoint provided"); + return; + } + } + + try { + double setpointTemp = targetTemp != null ? targetTemp : 0; + long setpointEnd = targetEndTime; + SetpointMode setpointMode = targetMode; + energy.ifPresent(cap -> cap.setRoomThermTemp(roomId, setpointTemp, setpointEnd, setpointMode)); + } catch (IllegalArgumentException e) { + logger.debug("Ignoring setRoomThermpoint command due to illegal argument exception: {}", + e.getMessage()); + } + } else { + logger.info("Handler not set for room thing actions."); + } + } + + /** + * Static setThermpoint method for Rules DSL backward compatibility + */ + public static void setThermpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime, + @Nullable String mode) { + ((RoomActions) actions).setThermpoint(temp, endTime, mode); + } + + public static void setThermpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime) { + setThermpoint(actions, temp, endTime, null); + } + + public static void setThermpoint(ThingActions actions, @Nullable String mode, @Nullable Long endTime) { + setThermpoint(actions, null, endTime, mode); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AircareApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AircareApi.java new file mode 100644 index 0000000000000..a2d41dbea3d4a --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AircareApi.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.api.dto.NAMain; +import org.openhab.binding.netatmo.internal.api.dto.NAMain.StationDataResponse; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; + +/** + * Base class for all Air Care related endpoints + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class AircareApi extends RestManager { + + public AircareApi(ApiBridgeHandler apiClient) { + super(apiClient, FeatureArea.AIR_CARE); + } + + /** + * Returns data from Healthy Home Coach Station (measures and device specific data). + * + * @param deviceId Id of the device you want to retrieve information of (optional) + * @return StationDataResponse + * @throws NetatmoException If fail to call the API, e.g. server error or deserializing + */ + public StationDataResponse getHomeCoachData(@Nullable String deviceId) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMECOACH, PARAM_DEVICEID, deviceId); + return get(uriBuilder, StationDataResponse.class); + } + + public NAMain getHomeCoach(String deviceId) throws NetatmoException { + ListBodyResponse answer = getHomeCoachData(deviceId).getBody(); + if (answer != null) { + NAMain station = answer.getElement(deviceId); + if (station != null) { + return station; + } + } + throw new NetatmoException("Unexpected answer querying device '%s' : not found.", deviceId); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiError.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiError.java new file mode 100644 index 0000000000000..5901c89784267 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiError.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError; + +/** + * The {@link ApiError} models an errored response from API + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class ApiError { + private class Body { + private String message = ""; + private ServiceError code = ServiceError.UNKNOWN; + } + + private Body error = new Body(); + + public String getMessage() { + return error.message; + } + + public ServiceError getCode() { + return error.code; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiResponse.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiResponse.java new file mode 100644 index 0000000000000..28aab2212203f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ApiResponse.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link ApiResponse} models a response returned by API call + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class ApiResponse { + /** + * The {@link Ok} models a response that only holds the result of the request sent to the API + */ + static class Ok extends ApiResponse { + private static final String SUCCESS = "ok"; + + boolean failed() { + return !SUCCESS.equals(getStatus()); + } + } + + private String status = ""; + private @Nullable T body; + + public String getStatus() { + return status; + } + + public @Nullable T getBody() { + return body; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java new file mode 100644 index 0000000000000..2da5144aef2c8 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.PATH_OAUTH; +import static org.openhab.core.auth.oauth2client.internal.Keyword.*; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope; +import org.openhab.binding.netatmo.internal.api.dto.AccessTokenResponse; +import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration.Credentials; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link AuthenticationApi} handles oAuth2 authentication and token refreshing + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class AuthenticationApi extends RestManager { + private static final URI OAUTH_URI = getApiBaseBuilder().path(PATH_OAUTH).build(); + + private final ScheduledExecutorService scheduler; + private final Logger logger = LoggerFactory.getLogger(AuthenticationApi.class); + + private @Nullable ScheduledFuture refreshTokenJob; + private Optional tokenResponse = Optional.empty(); + private String scope = ""; + + public AuthenticationApi(ApiBridgeHandler bridge, ScheduledExecutorService scheduler) { + super(bridge, FeatureArea.NONE); + this.scheduler = scheduler; + } + + public void authenticate(Credentials credentials, Set features) throws NetatmoException { + Set requestedFeatures = !features.isEmpty() ? features : FeatureArea.AS_SET; + scope = FeatureArea.toScopeString(requestedFeatures); + requestToken(credentials.clientId, credentials.clientSecret, + Map.of(USERNAME, credentials.username, PASSWORD, credentials.password, SCOPE, scope)); + } + + private void requestToken(String id, String secret, Map entries) throws NetatmoException { + Map payload = new HashMap<>(entries); + payload.putAll(Map.of(GRANT_TYPE, entries.keySet().contains(PASSWORD) ? PASSWORD : REFRESH_TOKEN, CLIENT_ID, id, + CLIENT_SECRET, secret)); + disconnect(); + AccessTokenResponse response = post(OAUTH_URI, AccessTokenResponse.class, payload); + refreshTokenJob = scheduler.schedule(() -> { + try { + requestToken(id, secret, Map.of(REFRESH_TOKEN, response.getRefreshToken())); + } catch (NetatmoException e) { + logger.warn("Unable to refresh access token : {}", e.getMessage()); + } + }, Math.round(response.getExpiresIn() * 0.8), TimeUnit.SECONDS); + tokenResponse = Optional.of(response); + } + + public void disconnect() { + tokenResponse = Optional.empty(); + } + + public void dispose() { + ScheduledFuture job = refreshTokenJob; + if (job != null) { + job.cancel(true); + } + refreshTokenJob = null; + } + + public @Nullable String getAuthorization() { + return tokenResponse.map(at -> String.format("Bearer %s", at.getAccessToken())).orElse(null); + } + + public boolean matchesScopes(Set requiredScopes) { + // either we do not require any scope, either connected and all scopes available + return requiredScopes.isEmpty() + || (isConnected() && tokenResponse.map(at -> at.getScope().containsAll(requiredScopes)).orElse(false)); + } + + public boolean isConnected() { + return !tokenResponse.isEmpty(); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/BodyResponse.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/BodyResponse.java new file mode 100644 index 0000000000000..93feac0f0cda4 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/BodyResponse.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link BodyResponse} models a response returned by API call containing + * a list of elements. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class BodyResponse { + @SerializedName(value = "home") + private @Nullable T element; + + public @Nullable T getElement() { + return element; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EnergyApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EnergyApi.java new file mode 100644 index 0000000000000..f5a36405dbbfb --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/EnergyApi.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; + +/** + * The {@link EnergyApi} handles API endpoints related to Energy feature area + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class EnergyApi extends RestManager { + public EnergyApi(ApiBridgeHandler apiClient) { + super(apiClient, FeatureArea.ENERGY); + } + + /** + * + * The method switchSchedule switches the home's schedule to another existing schedule. + * + * @param homeId The id of home (required) + * @param scheduleId The schedule id. It can be found in the getthermstate response, under the keys + * therm_program_backup and therm_program. (required) + * @throws NetatmoException If fail to call the API, e.g. server error or cannot deserialize the + * response body + */ + public void switchSchedule(String homeId, String scheduleId) throws NetatmoException { + UriBuilder uriBuilder = getAppUriBuilder(SUB_PATH_SWITCHSCHEDULE, PARAM_HOMEID, homeId, PARAM_SCHEDULEID, + scheduleId); + post(uriBuilder, ApiResponse.Ok.class, null, null); + } + + /** + * + * This endpoint permits to control the heating of a specific home. A home can be set in 3 differents modes: + * "schedule" mode in which the home will follow the user schedule + * "away" mode which will put the whole house to away (default is 12° but can be changed by the user in its + * settings) + * "hg" corresponds to frostguard mode (7° by default) + * + * @param homeId The id of home (required) + * @param mode The mode. (required) + * @throws NetatmoCommunicationException when call failed, e.g. server error or cannot deserialize + */ + public void setThermMode(String homeId, String mode) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_SETTHERMMODE, PARAM_HOMEID, homeId, PARAM_MODE, mode); + post(uriBuilder, ApiResponse.Ok.class, null, null); + } + + /** + * + * The method setThermpoint changes the Thermostat manual temperature setpoint. + * + * @param homeId The id of home (required) + * @param roomId The id of the room (required) + * @param mode The mode. (required) + * @param endtime For manual or max setpoint_mode, defines when the setpoint expires. + * @param temp For manual setpoint_mode, defines the temperature setpoint (in °C) + * @throws NetatmoCommunicationException when call failed, e.g. server error or cannot deserialize + */ + public void setThermpoint(String homeId, String roomId, SetpointMode mode, long endtime, double temp) + throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_SETROOMTHERMPOINT, PARAM_HOMEID, homeId, PARAM_ROOMID, roomId, + PARAM_MODE, mode.apiDescriptor); + if (mode == SetpointMode.MANUAL || mode == SetpointMode.MAX) { + uriBuilder.queryParam("endtime", endtime); + if (mode == SetpointMode.MANUAL) { + uriBuilder.queryParam("temp", temp > THERM_MAX_SETPOINT ? THERM_MAX_SETPOINT : temp); + } + } + post(uriBuilder, ApiResponse.Ok.class, null, null); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/HomeApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/HomeApi.java new file mode 100644 index 0000000000000..5ce35d0f1893c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/HomeApi.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*; + +import java.util.Collection; +import java.util.Set; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.api.dto.HomeData; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.NAHomeStatusResponse; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; + +/** + * The {@link HomeApi} handles general API endpoints not requiring specific scope area + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class HomeApi extends RestManager { + + public HomeApi(ApiBridgeHandler apiClient) { + super(apiClient, FeatureArea.NONE); + } + + public @Nullable HomeStatus getHomeStatus(String homeId) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMESTATUS, PARAM_HOMEID, homeId); + + NAHomeStatusResponse response = get(uriBuilder, NAHomeStatusResponse.class); + NAHomeStatus body = response.getBody(); + return body != null ? body.getHomeStatus().orElse(null) : null; + } + + public @Nullable HomeData getHomeData(String homeId) throws NetatmoException { + Collection result = getHomesData(homeId, null); + return result.isEmpty() ? null : result.iterator().next(); + } + + public Collection getHomesData(@Nullable String homeId, @Nullable ModuleType type) + throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMES_DATA, PARAM_HOMEID, homeId); + + if (type != null) { + uriBuilder.queryParam(PARAM_GATEWAYTYPE, type.name()); + } + + HomeData.HomesDataResponse response = get(uriBuilder, HomeData.HomesDataResponse.class); + ListBodyResponse body = response.getBody(); + return body != null ? body.getElements() : Set.of(); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ListBodyResponse.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ListBodyResponse.java new file mode 100644 index 0000000000000..2520dc6a7f4d7 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/ListBodyResponse.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import java.util.Collection; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link ListBodyResponse} models a response returned by API call containing + * a list of elements. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class ListBodyResponse { + @SerializedName(value = "devices", alternate = { "homes", "events_list", "events" }) + private NAObjectMap elements = new NAObjectMap<>(); + + @Nullable + T getElement(String id) { + return elements.get(id); + } + + public Collection getElements() { + return elements.values(); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoException.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoException.java new file mode 100644 index 0000000000000..709f205e80828 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/NetatmoException.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError; + +/** + * An exception that occurred while communicating with Netatmo server or related processes. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NetatmoException extends IOException { + private static final long serialVersionUID = 1513549973502021727L; + private ServiceError statusCode = ServiceError.UNKNOWN; + + public NetatmoException(String format, Object... args) { + super(String.format(format, args)); + } + + public NetatmoException(Exception e, String format, Object... args) { + super(String.format(format, args), e); + } + + public NetatmoException(String message) { + super(message); + } + + public NetatmoException(ApiError error) { + super(error.getMessage()); + this.statusCode = error.getCode(); + } + + public ServiceError getStatusCode() { + return statusCode; + } + + @Override + public @Nullable String getMessage() { + String message = super.getMessage(); + return message == null ? null + : String.format("Rest call failed: statusCode=%s, message=%s", statusCode, message); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/RestManager.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/RestManager.java new file mode 100644 index 0000000000000..c556a8a0a900f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/RestManager.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import static org.eclipse.jetty.http.HttpMethod.POST; +import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*; + +import java.net.URI; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.http.HttpMethod; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; + +/** + * Base class for all various rest managers + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public abstract class RestManager { + private static final UriBuilder API_BASE_BUILDER = UriBuilder.fromUri(URL_API); + private static final UriBuilder APP_URI_BUILDER = UriBuilder.fromUri(URL_APP).path(PATH_API); + private static final UriBuilder API_URI_BUILDER = getApiBaseBuilder().path(PATH_API); + + private final Set requiredScopes; + private final ApiBridgeHandler apiBridge; + + public RestManager(ApiBridgeHandler apiBridge, FeatureArea features) { + this.requiredScopes = features.scopes; + this.apiBridge = apiBridge; + } + + protected > T get(UriBuilder uriBuilder, Class clazz) throws NetatmoException { + return executeUri(uriBuilder, HttpMethod.GET, clazz, null, null); + } + + protected > T post(UriBuilder uriBuilder, Class clazz, @Nullable String payload, + @Nullable String contentType) throws NetatmoException { + return executeUri(uriBuilder, HttpMethod.POST, clazz, payload, contentType); + } + + protected T post(URI uri, Class clazz, Map entries) throws NetatmoException { + return apiBridge.executeUri(uri, POST, clazz, toRequest(entries), + "application/x-www-form-urlencoded;charset=UTF-8", 3); + } + + private > T executeUri(UriBuilder uriBuilder, HttpMethod method, Class clazz, + @Nullable String payload, @Nullable String contentType) throws NetatmoException { + URI uri = uriBuilder.build(); + T response = apiBridge.executeUri(uri, method, clazz, payload, contentType, 3); + if (response instanceof ApiResponse.Ok && ((ApiResponse.Ok) response).failed()) { + throw new NetatmoException("Command failed : %s for uri : %s", response.getStatus(), uri.toString()); + } + return response; + } + + private static UriBuilder appendParams(UriBuilder builder, @Nullable Object... params) { + if (params.length % 2 != 0) { + throw new IllegalArgumentException("appendParams : params count must be even"); + } + for (int i = 0; i < params.length; i += 2) { + Object query = params[i]; + if (query instanceof String) { + Object param = params[i + 1]; + if (param != null) { + builder.queryParam((String) query, param); + } + } else { + throw new IllegalArgumentException("appendParams : even parameters must be Strings"); + } + } + return builder; + } + + protected static UriBuilder getApiBaseBuilder() { + return API_BASE_BUILDER.clone(); + } + + public static UriBuilder getApiUriBuilder(String path, @Nullable Object... params) { + return appendParams(API_URI_BUILDER.clone().path(path), params); + } + + protected static UriBuilder getAppUriBuilder(String path, @Nullable Object... params) { + return appendParams(APP_URI_BUILDER.clone().path(path), params); + } + + private String toRequest(Map entries) { + return entries.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&")); + } + + public Set getRequiredScopes() { + return requiredScopes; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/SecurityApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/SecurityApi.java new file mode 100644 index 0000000000000..3640144ff4902 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/SecurityApi.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*; + +import java.net.URI; +import java.util.Collection; +import java.util.stream.Collectors; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode; +import org.openhab.binding.netatmo.internal.api.dto.Home; +import org.openhab.binding.netatmo.internal.api.dto.HomeEvent; +import org.openhab.binding.netatmo.internal.api.dto.HomeEvent.NAEventsDataResponse; +import org.openhab.binding.netatmo.internal.api.dto.Ping; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; + +/** + * Base class for all Security related endpoints + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class SecurityApi extends RestManager { + public SecurityApi(ApiBridgeHandler apiClient) { + super(apiClient, FeatureArea.SECURITY); + } + + /** + * Dissociates a webhook from a user. + * + * @throws NetatmoException If fail to call the API, e.g. server error or deserializing + */ + public void dropWebhook() throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_DROPWEBHOOK); + post(uriBuilder, ApiResponse.Ok.class, null, null); + } + + /** + * Links a callback url to a user. + * + * @param uri Your webhook callback url (required) + * @throws NetatmoException If fail to call the API, e.g. server error or deserializing + */ + public void addwebhook(URI uri) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_ADDWEBHOOK, PARAM_URL, uri.toString()); + post(uriBuilder, ApiResponse.Ok.class, null, null); + } + + public Collection getPersonEvents(String homeId, String personId) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETEVENTS, PARAM_HOMEID, homeId, PARAM_PERSONID, personId, + PARAM_OFFSET, 1); + NAEventsDataResponse response = get(uriBuilder, NAEventsDataResponse.class); + BodyResponse body = response.getBody(); + if (body != null) { + Home home = body.getElement(); + if (home != null) { + return home.getEvents().stream().filter(event -> personId.equals(event.getPersonId())) + .collect(Collectors.toList()); + } + } + throw new NetatmoException("home should not be null"); + } + + public Collection getCameraEvents(String homeId, String cameraId) throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETEVENTS, PARAM_HOMEID, homeId, PARAM_DEVICEID, cameraId); + NAEventsDataResponse response = get(uriBuilder, NAEventsDataResponse.class); + BodyResponse body = response.getBody(); + if (body != null) { + Home home = body.getElement(); + if (home != null) { + return home.getEvents(); + } + } + throw new NetatmoException("home should not be null"); + } + + public String ping(String vpnUrl) throws NetatmoException { + UriBuilder uriBuilder = UriBuilder.fromUri(vpnUrl).path(PATH_COMMAND).path(SUB_PATH_PING); + Ping response = get(uriBuilder, Ping.class); + return response.getStatus(); + } + + public void changeStatus(String localCameraURL, boolean setOn) throws NetatmoException { + UriBuilder uriBuilder = UriBuilder.fromUri(localCameraURL).path(PATH_COMMAND).path(SUB_PATH_CHANGESTATUS); + uriBuilder.queryParam(PARAM_STATUS, setOn ? "on" : "off"); + post(uriBuilder, ApiResponse.Ok.class, null, null); + } + + public void changeFloodLightMode(String localCameraURL, FloodLightMode mode) throws NetatmoException { + UriBuilder uriBuilder = UriBuilder.fromUri(localCameraURL).path(PATH_COMMAND).path(SUB_PATH_FLOODLIGHTSET); + uriBuilder.queryParam("config", "%7B%22mode%22:%22" + mode.toString() + "%22%7D"); + get(uriBuilder, ApiResponse.Ok.class); + } + + public void setPersonAwayStatus(String homeId, String personId, boolean away) throws NetatmoException { + UriBuilder uriBuilder = getAppUriBuilder(away ? SUB_PATH_PERSON_AWAY : SUB_PATH_PERSON_HOME); + String payload = String.format( + away ? "{\"home_id\":\"%s\",\"person_id\":\"%s\"}" : "{\"home_id\":\"%s\",\"person_ids\":[\"%s\"]}", + homeId, personId); + post(uriBuilder, ApiResponse.Ok.class, payload, "application/json;charset=utf-8"); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java new file mode 100644 index 0000000000000..336b432b63c7f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/WeatherApi.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*; + +import java.time.ZonedDateTime; +import java.util.List; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.api.dto.MeasureBodyElem; +import org.openhab.binding.netatmo.internal.api.dto.NAMain; +import org.openhab.binding.netatmo.internal.api.dto.NAMain.StationDataResponse; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; + +/** + * Base class for all Weather related endpoints + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class WeatherApi extends RestManager { + private class NAMeasuresResponse extends ApiResponse>> { + } + + private class NADateMeasuresResponse extends ApiResponse>> { + } + + public WeatherApi(ApiBridgeHandler apiClient) { + super(apiClient, FeatureArea.WEATHER); + } + + /** + * + * Returns data from a user's Weather Stations (measures and device specific data); + * + * @param deviceId Id of the device you want to retrieve information of (optional) + * @param getFavorites Whether to include the user's favorite Weather Stations in addition to the user's + * own Weather Stations (optional, default to false) + * @return StationDataResponse + * @throws NetatmoException If fail to call the API, e.g. server error or deserializing + */ + public StationDataResponse getStationsData(@Nullable String deviceId, boolean getFavorites) + throws NetatmoException { + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETSTATION, PARAM_DEVICEID, deviceId, // + PARAM_FAVORITES, getFavorites); + StationDataResponse response = get(uriBuilder, StationDataResponse.class); + return response; + } + + public NAMain getStationData(String deviceId) throws NetatmoException { + ListBodyResponse answer = getStationsData(deviceId, true).getBody(); + if (answer != null) { + NAMain station = answer.getElement(deviceId); + if (station != null) { + return station; + } + } + throw new NetatmoException("Unexpected answer searching device '%s' : not found.", deviceId); + } + + public @Nullable Object getMeasures(String deviceId, @Nullable String moduleId, @Nullable String scale, + String apiDescriptor) throws NetatmoException { + MeasureBodyElem result = getMeasure(deviceId, moduleId, scale, apiDescriptor); + return result.getSingleValue(); + } + + public @Nullable Object getMeasures(String deviceId, @Nullable String moduleId, @Nullable String scale, + String apiDescriptor, String limit) throws NetatmoException { + String queryLimit = limit; + if (!apiDescriptor.contains("_")) { + queryLimit += "_" + apiDescriptor; + } + + MeasureBodyElem result = getMeasure(deviceId, moduleId, scale, queryLimit.toLowerCase()); + return result.getSingleValue(); + } + + private MeasureBodyElem getMeasure(String deviceId, @Nullable String moduleId, @Nullable String scale, + String measureType) throws NetatmoException { + // NAMeasuresResponse is not designed for optimize=false + UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETMEASURE, PARAM_DEVICEID, deviceId, "real_time", true, + "date_end", "last", "optimize", true, "type", measureType.toLowerCase(), PARAM_MODULEID, moduleId); + + if (scale != null) { + uriBuilder.queryParam("scale", scale.toLowerCase()); + } + if (measureType.startsWith("date")) { + NADateMeasuresResponse response = get(uriBuilder, NADateMeasuresResponse.class); + List> body = response.getBody(); + if (body != null && !body.isEmpty()) { + return body.get(0); + } + } else { + NAMeasuresResponse response = get(uriBuilder, NAMeasuresResponse.class); + List> body = response.getBody(); + if (body != null && !body.isEmpty()) { + return body.get(0); + } + } + throw new NetatmoException("Empty response while getting measurements"); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java new file mode 100644 index 0000000000000..e48f4f3292582 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.data; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * This enum describes sub events in relation to a given event + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public enum EventSubType { + SD_CARD_MISSING(List.of(EventType.SD), 1), + SD_CARD_INSERTED(List.of(EventType.SD), 2), + SD_CARD_FORMATTED(List.of(EventType.SD), 3), + SD_CARD_WORKING(List.of(EventType.SD), 4), + SD_CARD_DEFECTIVE(List.of(EventType.SD), 5), + SD_CARD_INCOMPATIBLE_SPEED(List.of(EventType.SD), 6), + SD_CARD_INSUFFICIENT_SPACE(List.of(EventType.SD), 7), + ALIM_INCORRECT_POWER(List.of(EventType.ALIM), 1), + ALIM_CORRECT_POWER(List.of(EventType.ALIM), 2), + + // Artificially implemented by the binding subtypes + PERSON_ARRIVAL(List.of(EventType.PERSON, EventType.PERSON_HOME), 1), + PERSON_SEEN(List.of(EventType.PERSON), 2), + PERSON_DEPARTURE(List.of(EventType.PERSON_AWAY), 1), + MOVEMENT_HUMAN(List.of(EventType.MOVEMENT, EventType.HUMAN), 1), + MOVEMENT_VEHICLE(List.of(EventType.MOVEMENT), 2), + MOVEMENT_ANIMAL(List.of(EventType.MOVEMENT, EventType.ANIMAL), 3); + + public final List types; + public final int subType; + + EventSubType(List types, int i) { + this.types = types; + this.subType = i; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java new file mode 100644 index 0000000000000..6f48291f73906 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.data; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.annotations.SerializedName; + +/** + * This enum describes events generated by webhooks and the type of + * module they are related to according to API documentation + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public enum EventType { + UNKNOWN(), + + @SerializedName("person") // When the Indoor Camera detects a face + PERSON(ModuleType.PERSON, ModuleType.WELCOME), + + @SerializedName("person_away") // When geofencing indicates that the person has left the home + PERSON_AWAY(ModuleType.PERSON, ModuleType.HOME), + + @SerializedName("person_home") // When the person is declared at home + PERSON_HOME(ModuleType.PERSON, ModuleType.HOME), + + @SerializedName("outdoor") // When the Outdoor Camera detects a human, a car or an animal + OUTDOOR(ModuleType.PRESENCE, ModuleType.DOORBELL), + + @SerializedName("daily_summary") // When the Outdoor Camera video summary of the last 24 hours is available + DAILY_SUMMARY(ModuleType.PRESENCE), + + @SerializedName("movement") // When the Indoor Camera detects motion + MOVEMENT(ModuleType.WELCOME), + + @SerializedName("human") // When the Indoor Camera detects human motion + HUMAN(ModuleType.WELCOME), + + @SerializedName("animal") // When the Indoor Camera detects animal motion + ANIMAL(ModuleType.WELCOME), + + @SerializedName("new_module") // A new Module has been paired with the Indoor Camera + NEW_MODULE(ModuleType.WELCOME), + + @SerializedName("module_connect") // Module is connected with the Indoor Camera + MODULE_CONNECT(ModuleType.WELCOME), + + @SerializedName("module_disconnect") // Module lost its connection with the Indoor Camera + MODULE_DISCONNECT(ModuleType.WELCOME), + + @SerializedName("module_low_battery") // Module's battery is low + MODULE_LOW_BATTERY(ModuleType.WELCOME), + + @SerializedName("module_end_update") // Module's firmware update is over + MODULE_END_UPDATE(ModuleType.WELCOME), + + @SerializedName("connection") // When the Camera connects to Netatmo servers + CONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE), + + @SerializedName("disconnection") // When the Camera loses connection with Netatmo servers + DISCONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE), + + @SerializedName("on") // When Camera Monitoring is resumed + ON(ModuleType.WELCOME, ModuleType.PRESENCE), + + @SerializedName("off") // When Camera Monitoring is turned off + OFF(ModuleType.WELCOME, ModuleType.PRESENCE), + + @SerializedName("boot") // When the Camera is booting + BOOT(ModuleType.WELCOME, ModuleType.PRESENCE), + + @SerializedName("sd") // When Camera SD Card status changes + SD(ModuleType.WELCOME, ModuleType.PRESENCE), + + @SerializedName("alim") // When Camera power supply status changes + ALIM(ModuleType.WELCOME, ModuleType.PRESENCE); + + private final Set appliesTo; + + EventType(ModuleType... appliesTo) { + this.appliesTo = Set.of(appliesTo); + } + + @Override + public String toString() { + return name().toLowerCase(); + } + + public boolean appliesOn(ModuleType searched) { + return appliesTo.contains(searched); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java new file mode 100644 index 0000000000000..82a2c7e7b63e1 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java @@ -0,0 +1,225 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.data; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID; +import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*; + +import java.net.URI; +import java.util.EnumSet; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.handler.capability.AirCareCapability; +import org.openhab.binding.netatmo.internal.handler.capability.CameraCapability; +import org.openhab.binding.netatmo.internal.handler.capability.Capability; +import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability; +import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability; +import org.openhab.binding.netatmo.internal.handler.capability.EventCapability; +import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability; +import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability; +import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability; +import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability; +import org.openhab.binding.netatmo.internal.handler.capability.RoomCapability; +import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability; +import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityExtChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.BatteryChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.BatteryExtChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.EventChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.EventPersonChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.HomeEnergyChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.HomeSecurityChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.HumidityChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.LocationChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.MeasuresChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.NoiseChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.PersonChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.PresenceChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.PressureChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.PressureExtChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.RainChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.RoomChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.SetpointChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.SignalChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureExtChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureOutChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.Therm1ChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampExtChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.WindChannelHelper; +import org.openhab.core.thing.ThingTypeUID; + +import com.google.gson.annotations.SerializedName; + +/** + * This enum all handled Netatmo modules and devices along with their capabilities + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public enum ModuleType { + UNKNOWN(FeatureArea.NONE, null, null, List.of(), List.of()), + ACCOUNT(FeatureArea.NONE, null, null, List.of(), List.of()), + @SerializedName("NAHome") + HOME(FeatureArea.NONE, "NAHome", ACCOUNT, + List.of(DeviceCapability.class, EventCapability.class, HomeCapability.class, ChannelHelperCapability.class), + List.of(HomeSecurityChannelHelper.class, HomeEnergyChannelHelper.class)), + @SerializedName("NAPerson") + PERSON(FeatureArea.SECURITY, "NAPerson", HOME, + List.of(EventCapability.class, PersonCapability.class, ChannelHelperCapability.class), + List.of(PersonChannelHelper.class, EventPersonChannelHelper.class)), + @SerializedName("NACamera") + WELCOME(FeatureArea.SECURITY, "NACamera", HOME, + List.of(EventCapability.class, CameraCapability.class, ChannelHelperCapability.class), + List.of(CameraChannelHelper.class, SignalChannelHelper.class, EventChannelHelper.class)), + @SerializedName("NOC") + PRESENCE(FeatureArea.SECURITY, "NOC", HOME, + List.of(EventCapability.class, PresenceCapability.class, ChannelHelperCapability.class), + List.of(CameraChannelHelper.class, PresenceChannelHelper.class, SignalChannelHelper.class, + EventChannelHelper.class)), + @SerializedName("NIS") + SIREN(FeatureArea.SECURITY, "NIS", HOME, List.of(ChannelHelperCapability.class), + List.of(BatteryChannelHelper.class, TimestampChannelHelper.class, SignalChannelHelper.class)), + @SerializedName("NDB") + DOORBELL(FeatureArea.SECURITY, "NDB", HOME, List.of(ChannelHelperCapability.class), + List.of(SignalChannelHelper.class)), + @SerializedName("NAMain") + WEATHER_STATION(FeatureArea.WEATHER, "NAMain", ACCOUNT, + List.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class, + ChannelHelperCapability.class), + List.of(PressureExtChannelHelper.class, NoiseChannelHelper.class, HumidityChannelHelper.class, + TemperatureExtChannelHelper.class, AirQualityChannelHelper.class, LocationChannelHelper.class, + TimestampExtChannelHelper.class, MeasuresChannelHelper.class, SignalChannelHelper.class)), + @SerializedName("NAModule1") + OUTDOOR(FeatureArea.WEATHER, "NAModule1", WEATHER_STATION, + List.of(MeasureCapability.class, ChannelHelperCapability.class), + List.of(HumidityChannelHelper.class, TemperatureOutChannelHelper.class, BatteryChannelHelper.class, + MeasuresChannelHelper.class, TimestampExtChannelHelper.class, SignalChannelHelper.class)), + @SerializedName("NAModule2") + WIND(FeatureArea.WEATHER, "NAModule2", WEATHER_STATION, List.of(ChannelHelperCapability.class), + List.of(WindChannelHelper.class, BatteryChannelHelper.class, TimestampExtChannelHelper.class, + SignalChannelHelper.class)), + @SerializedName("NAModule3") + RAIN(FeatureArea.WEATHER, "NAModule3", WEATHER_STATION, + List.of(MeasureCapability.class, ChannelHelperCapability.class), + List.of(RainChannelHelper.class, BatteryChannelHelper.class, MeasuresChannelHelper.class, + TimestampExtChannelHelper.class, SignalChannelHelper.class)), + @SerializedName("NAModule4") + INDOOR(FeatureArea.WEATHER, "NAModule4", WEATHER_STATION, + List.of(MeasureCapability.class, ChannelHelperCapability.class), + List.of(HumidityChannelHelper.class, TemperatureExtChannelHelper.class, AirQualityChannelHelper.class, + BatteryChannelHelper.class, MeasuresChannelHelper.class, TimestampExtChannelHelper.class, + SignalChannelHelper.class)), + @SerializedName("NHC") + HOME_COACH(FeatureArea.AIR_CARE, "NHC", ACCOUNT, + List.of(DeviceCapability.class, AirCareCapability.class, MeasureCapability.class, + ChannelHelperCapability.class), + List.of(NoiseChannelHelper.class, HumidityChannelHelper.class, AirQualityExtChannelHelper.class, + TemperatureChannelHelper.class, PressureChannelHelper.class, TimestampExtChannelHelper.class, + SignalChannelHelper.class, MeasuresChannelHelper.class, LocationChannelHelper.class)), + @SerializedName("NAPlug") + PLUG(FeatureArea.ENERGY, "NAPlug", HOME, List.of(ChannelHelperCapability.class), + List.of(SignalChannelHelper.class)), + @SerializedName("NATherm1") + THERMOSTAT(FeatureArea.ENERGY, "NATherm1", HOME, List.of(ChannelHelperCapability.class), + List.of(Therm1ChannelHelper.class, BatteryExtChannelHelper.class, SignalChannelHelper.class)), + @SerializedName("NARoom") + ROOM(FeatureArea.ENERGY, "NARoom", HOME, List.of(RoomCapability.class, ChannelHelperCapability.class), + List.of(RoomChannelHelper.class, SetpointChannelHelper.class)), + @SerializedName("NRV") + VALVE(FeatureArea.ENERGY, "NRV", HOME, List.of(ChannelHelperCapability.class), + List.of(BatteryExtChannelHelper.class, SignalChannelHelper.class)); + + public static final EnumSet AS_SET = EnumSet.allOf(ModuleType.class); + + private final @Nullable ModuleType bridgeType; + public final List groupTypes = new LinkedList<>(); + public final List extensions = new LinkedList<>(); + public final List> channelHelpers; + public final List> capabilities; + public final ThingTypeUID thingTypeUID; + public final FeatureArea feature; + public final @Nullable String apiName; + + ModuleType(FeatureArea feature, @Nullable String apiName, @Nullable ModuleType bridge, + List> capabilities, List> helpers) { + this.channelHelpers = helpers; + this.bridgeType = bridge; + this.feature = feature; + this.capabilities = capabilities; + this.apiName = apiName; + thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase().replace("_", "-")); + try { + for (Class helperClass : helpers) { + ChannelHelper helper = helperClass.getConstructor().newInstance(); + groupTypes.addAll(helper.getChannelGroupTypes()); + extensions.addAll(helper.getExtensibleChannels()); + } + } catch (RuntimeException | ReflectiveOperationException e) { + throw new IllegalArgumentException(e); + } + } + + public boolean isLogical() { + return !channelHelpers.contains(SignalChannelHelper.class); + } + + public boolean isABridge() { + for (ModuleType mt : ModuleType.values()) { + if (this.equals(mt.bridgeType)) { + return true; + } + } + return false; + } + + public int[] getSignalLevels() { + if (!isLogical()) { + return (channelHelpers.contains(BatteryChannelHelper.class) + || channelHelpers.contains(BatteryExtChannelHelper.class)) ? RADIO_SIGNAL_LEVELS + : WIFI_SIGNAL_LEVELS; + } + throw new IllegalArgumentException( + "This should not be called for module type : " + name() + ", please file a bug report."); + } + + public ModuleType getBridge() { + ModuleType bridge = bridgeType; + return bridge != null ? bridge : ModuleType.UNKNOWN; + } + + public URI getConfigDescription() { + return URI.create(BINDING_ID + ":" + + (equals(ACCOUNT) ? "api_bridge" + : equals(HOME) ? "home" + : (isLogical() ? "virtual" + : ModuleType.UNKNOWN.equals(getBridge()) ? "configurable" : "device"))); + } + + public static ModuleType from(ThingTypeUID thingTypeUID) { + return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst() + .orElseThrow(() -> new IllegalArgumentException()); + } + + public static ModuleType from(String apiName) { + return ModuleType.AS_SET.stream().filter(mt -> apiName.equals(mt.apiName)).findFirst() + .orElseThrow(() -> new IllegalArgumentException()); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java new file mode 100644 index 0000000000000..65829a0e18c88 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java @@ -0,0 +1,401 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.data; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.core.library.CoreItemFactory.*; +import static org.openhab.core.library.unit.MetricPrefix.*; + +import java.net.URI; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.StateDescriptionFragment; +import org.openhab.core.types.StateDescriptionFragmentBuilder; +import org.openhab.core.types.util.UnitUtils; + +import com.google.gson.annotations.SerializedName; + +/** + * This class holds various definitions and settings provided by the Netatmo + * API documentation + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NetatmoConstants { + public static class Measure { + public final double minValue; + public final double maxValue; + public final int scale; + public final Unit unit; + + private Measure(double minValue, double maxValue, double precision, Unit unit) { + this.minValue = minValue; + this.maxValue = maxValue; + this.unit = unit; + String[] splitter = Double.valueOf(precision).toString().split("\\."); + if (splitter.length > 1) { + int dec = Integer.parseInt(splitter[1]); + this.scale = dec > 0 ? Integer.toString(dec).length() : 0; + } else { + this.scale = 0; + } + } + } + + public static class MeasureChannelDetails { + private static final StateDescriptionFragmentBuilder BUILDER = StateDescriptionFragmentBuilder.create(); + public final URI configURI; + public final String itemType; + public final StateDescriptionFragment stateDescriptionFragment; + + private MeasureChannelDetails(String measureType, String itemType, String pattern) { + this.configURI = URI.create(String.join(":", BINDING_ID, measureType, "config")); + this.itemType = itemType; + this.stateDescriptionFragment = BUILDER.withReadOnly(true).withPattern(pattern).build(); + } + } + + public enum MeasureClass { + INSIDE_TEMPERATURE(0, 50, 0.3, SIUnits.CELSIUS, "temp", "measure", true), + OUTSIDE_TEMPERATURE(-40, 65, 0.3, SIUnits.CELSIUS, "temp", "measure", true), + HEAT_INDEX(-40, 65, 1, SIUnits.CELSIUS, "", "", false), + PRESSURE(260, 1260, 1, HECTO(SIUnits.PASCAL), "pressure", "measure", true), + CO2(0, 5000, 50, Units.PARTS_PER_MILLION, "co2", "measure", true), + NOISE(35, 120, 1, Units.DECIBEL, "noise", "measure", true), + RAIN_QUANTITY(0, 150, 0.1, MILLI(SIUnits.METRE), "sum_rain", "sum_rain", false), + RAIN_INTENSITY(0, 150, 0.1, Units.MILLIMETRE_PER_HOUR, "", "", false), + WIND_SPEED(0, 160, 1.8, SIUnits.KILOMETRE_PER_HOUR, "", "", false), + WIND_ANGLE(0, 360, 5, Units.DEGREE_ANGLE, "", "", false), + HUMIDITY(0, 100, 3, Units.PERCENT, "hum", "measure", true); + + public static final EnumSet AS_SET = EnumSet.allOf(MeasureClass.class); + + public final Measure measureDefinition; + public final String apiDescriptor; + public final Map channels = new HashMap<>(2); + + MeasureClass(double min, double max, double precision, Unit unit, String apiDescriptor, String confFragment, + boolean canScale) { + this.measureDefinition = new Measure(min, max, precision, unit); + this.apiDescriptor = apiDescriptor; + if (!apiDescriptor.isBlank()) { + String dimension = UnitUtils.getDimensionName(unit); + + channels.put(String.join("-", apiDescriptor, "measurement"), + new MeasureChannelDetails(confFragment, String.join(":", NUMBER, dimension), + String.format("%%.%df %s", measureDefinition.scale, UnitUtils.UNIT_PLACEHOLDER))); + if (canScale) { + channels.put(String.join("-", apiDescriptor, GROUP_TIMESTAMP), + new MeasureChannelDetails(GROUP_TIMESTAMP, DATETIME, "%1$tA, %1$td.%1$tm. %1$tH:%1$tM")); + } + } + } + } + + // Netatmo API urls + public static final String URL_API = "https://api.netatmo.com/"; + public static final String URL_APP = "https://app.netatmo.net/"; + public static final String PATH_OAUTH = "oauth2/token"; + public static final String PATH_API = "api"; + public static final String PATH_COMMAND = "command"; + public static final String SUB_PATH_PERSON_AWAY = "setpersonsaway"; + public static final String SUB_PATH_PERSON_HOME = "setpersonshome"; + public static final String SUB_PATH_HOMES_DATA = "homesdata"; + public static final String SUB_PATH_ADDWEBHOOK = "addwebhook"; + public static final String SUB_PATH_DROPWEBHOOK = "dropwebhook"; + public static final String SUB_PATH_SETROOMTHERMPOINT = "setroomthermpoint"; + public static final String SUB_PATH_SETTHERMMODE = "setthermmode"; + public static final String SUB_PATH_SWITCHSCHEDULE = "switchschedule"; + public static final String SUB_PATH_GETSTATION = "getstationsdata"; + public static final String SUB_PATH_GETMEASURE = "getmeasure"; + public static final String SUB_PATH_HOMESTATUS = "homestatus"; + public static final String SUB_PATH_HOMECOACH = "gethomecoachsdata"; + public static final String SUB_PATH_GETEVENTS = "getevents"; + public static final String SUB_PATH_PING = "ping"; + public static final String SUB_PATH_CHANGESTATUS = "changestatus"; + public static final String SUB_PATH_FLOODLIGHTSET = "floodlight_set_config"; + public static final String PARAM_DEVICEID = "device_id"; + public static final String PARAM_MODULEID = "module_id"; + public static final String PARAM_HOMEID = "home_id"; + public static final String PARAM_ROOMID = "room_id"; + public static final String PARAM_PERSONID = "person_id"; + public static final String PARAM_SCHEDULEID = "schedule_id"; + public static final String PARAM_OFFSET = "offset"; + public static final String PARAM_GATEWAYTYPE = "gateway_types"; + public static final String PARAM_MODE = "mode"; + public static final String PARAM_URL = "url"; + public static final String PARAM_FAVORITES = "get_favorites"; + public static final String PARAM_STATUS = "status"; + + // Global variables + public static final int THERM_MAX_SETPOINT = 30; + + // Token scopes + public static enum Scope { + @SerializedName("read_station") + READ_STATION, + @SerializedName("read_thermostat") + READ_THERMOSTAT, + @SerializedName("write_thermostat") + WRITE_THERMOSTAT, + @SerializedName("read_camera") + READ_CAMERA, + @SerializedName("write_camera") + WRITE_CAMERA, + @SerializedName("access_camera") + ACCESS_CAMERA, + @SerializedName("read_presence") + READ_PRESENCE, + @SerializedName("write_presence") + WRITE_PRESENCE, + @SerializedName("access_presence") + ACCESS_PRESENCE, + @SerializedName("read_smokedetector") + READ_SMOKEDETECTOR, + @SerializedName("read_homecoach") + READ_HOMECOACH, + @SerializedName("read_doorbell") + READ_DOORBELL, + @SerializedName("write_doorbell") + WRITE_DOORBELL, + @SerializedName("access_doorbell") + ACCESS_DOORBELL, + UNKNOWN; + } + + private static final Set SMOKE = Set.of(Scope.READ_SMOKEDETECTOR); + private static final Set WELCOME = Set.of(Scope.READ_CAMERA, Scope.WRITE_CAMERA, Scope.ACCESS_CAMERA); + private static final Set DOORBELL = Set.of(Scope.READ_DOORBELL, Scope.WRITE_DOORBELL, Scope.ACCESS_DOORBELL); + private static final Set PRESENCE = Set.of(Scope.READ_PRESENCE, Scope.WRITE_PRESENCE, Scope.ACCESS_PRESENCE); + + // Radio signal quality thresholds + static final int[] WIFI_SIGNAL_LEVELS = new int[] { 99, 84, 69, 54 }; // Resp : bad, average, good, full + static final int[] RADIO_SIGNAL_LEVELS = new int[] { 90, 80, 70, 60 }; // Resp : low, medium, high, full + + public static enum FeatureArea { + AIR_CARE(Scope.READ_HOMECOACH), + WEATHER(Scope.READ_STATION), + ENERGY(Scope.READ_THERMOSTAT, Scope.WRITE_THERMOSTAT), + SECURITY(Stream.of(WELCOME, PRESENCE, SMOKE, DOORBELL).flatMap(Set::stream).toArray(Scope[]::new)), + NONE(); + + public static final Set AS_SET = EnumSet.allOf(FeatureArea.class); + + public static String toScopeString(Set featureSet) { + return featureSet.stream().map(fa -> fa.scopes).flatMap(Set::stream).map(s -> s.name().toLowerCase()) + .collect(Collectors.joining(" ")); + } + + public final Set scopes; + + FeatureArea(Scope... scopes) { + this.scopes = Set.of(scopes); + } + } + + // Thermostat definitions + public static enum SetpointMode { + @SerializedName("program") + PROGRAM("program"), + @SerializedName("away") + AWAY("away"), + @SerializedName("hg") + FROST_GUARD("hg"), + @SerializedName("manual") + MANUAL("manual"), + @SerializedName("off") + OFF("off"), + @SerializedName("max") + MAX("max"), + @SerializedName("schedule") + SCHEDULE("schedule"), + HOME("home"), + UNKNOWN(""); + + public final String apiDescriptor; + + SetpointMode(String descriptor) { + this.apiDescriptor = descriptor; + } + } + + public static enum ThermostatZoneType { + @SerializedName("0") + DAY("0"), + @SerializedName("1") + NIGHT("1"), + @SerializedName("2") + AWAY("2"), + @SerializedName("3") + FROST_GUARD("3"), + @SerializedName("4") + CUSTOM("4"), + @SerializedName("5") + ECO("5"), + @SerializedName("8") + COMFORT("8"), + UNKNOWN(""); + + public final String zoneId; + + private ThermostatZoneType(String id) { + zoneId = id; + } + } + + public enum FloodLightMode { + @SerializedName("on") + ON, + @SerializedName("off") + OFF, + @SerializedName("auto") + AUTO, + UNKNOWN; + } + + public enum EventCategory { + @SerializedName("human") + HUMAN, + @SerializedName("animal") + ANIMAL, + @SerializedName("vehicle") + VEHICLE, + UNKNOWN; + } + + public enum TrendDescription { + @SerializedName("up") + UP, + @SerializedName("stable") + STABLE, + @SerializedName("down") + DOWN, + UNKNOWN; + } + + public enum VideoStatus { + @SerializedName("recording") + RECORDING, + @SerializedName("available") + AVAILABLE, + @SerializedName("deleted") + DELETED, + UNKNOWN; + } + + public enum SdCardStatus { + @SerializedName("1") + SD_CARD_MISSING, + @SerializedName("2") + SD_CARD_INSERTED, + @SerializedName("3") + SD_CARD_FORMATTED, + @SerializedName("4") + SD_CARD_WORKING, + @SerializedName("5") + SD_CARD_DEFECTIVE, + @SerializedName("6") + SD_CARD_INCOMPATIBLE_SPEED, + @SerializedName("7") + SD_CARD_INSUFFICIENT_SPACE, + UNKNOWN; + } + + public enum AlimentationStatus { + @SerializedName("1") + ALIM_INCORRECT_POWER, + @SerializedName("2") + ALIM_CORRECT_POWER, + UNKNOWN; + } + + public enum BatteryState { + @SerializedName("full") + FULL(100), + @SerializedName("high") + HIGH(80), + @SerializedName("medium") + MEDIUM(50), + @SerializedName("low") + LOW(15), + UNKNOWN(-1); + + public final int level; + + BatteryState(int i) { + this.level = i; + } + } + + public enum ServiceError { + @SerializedName("99") + UNKNOWN, + @SerializedName("-2") + UNKNOWN_ERROR_IN_OAUTH, + @SerializedName("-1") + GRANT_IS_INVALID, + @SerializedName("1") + ACCESS_TOKEN_MISSING, + @SerializedName("2") + INVALID_TOKEN_MISSING, + @SerializedName("3") + ACCESS_TOKEN_EXPIRED, + @SerializedName("5") + APPLICATION_DEACTIVATED, + @SerializedName("7") + NOTHING_TO_MODIFY, + @SerializedName("9") + DEVICE_NOT_FOUND, + @SerializedName("10") + MISSING_ARGUMENTS, + @SerializedName("13") + OPERATION_FORBIDDEN, + @SerializedName("19") + IP_NOT_FOUND, + @SerializedName("21") + INVALID_ARGUMENT, + @SerializedName("22") + APPLICATION_NOT_FOUND, + @SerializedName("23") + USER_NOT_FOUND, + @SerializedName("25") + INVALID_DATE, + @SerializedName("26") + MAXIMUM_USAGE_REACHED, + @SerializedName("30") + INVALID_REFRESH_TOKEN, + @SerializedName("31") + METHOD_NOT_FOUND, + @SerializedName("35") + UNABLE_TO_EXECUTE, + @SerializedName("36") + PROHIBITED_STRING, + @SerializedName("37") + NO_MORE_SPACE_AVAILABLE_ON_THE_CAMERA, + @SerializedName("40") + JSON_GIVEN_HAS_AN_INVALID_ENCODING, + @SerializedName("41") + DEVICE_IS_UNREACHABLE; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/AccessTokenResponse.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/AccessTokenResponse.java new file mode 100644 index 0000000000000..65bd17457cadf --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/AccessTokenResponse.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.List; + +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope; + +/** + * This is the Access Token Response, a simple value-object holding the result of an Access Token Request, as + * provided by Netatmo API. + * + * @author Gaël L'hopital - Initial contribution + */ +public final class AccessTokenResponse { + + /** + * The access token issued by the authorization server. It is used + * by the client to gain access to a resource. + * + */ + private String accessToken; + + /** + * Number of seconds that this OAuthToken is valid for since the time it was created. + * + */ + private long expiresIn; + + /** + * Refresh token is a string representing the authorization granted to + * the client by the resource owner. Unlike access tokens, refresh tokens are + * intended for use only with authorization servers and are never sent + * to resource servers. + * + */ + private String refreshToken; + + private List scope; + + public String getAccessToken() { + return accessToken; + } + + public long getExpiresIn() { + return expiresIn; + } + + public String getRefreshToken() { + return refreshToken; + } + + public List getScope() { + return scope; + } + + @Override + public String toString() { + return "AccessTokenResponse [accessToken=" + accessToken + ", expiresIn=" + expiresIn + ", refreshToken=" + + refreshToken + ", scope=" + scope + "]"; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Dashboard.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Dashboard.java new file mode 100644 index 0000000000000..78ed869594da9 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Dashboard.java @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.TrendDescription; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link Dashboard} holds data returned by API call supporting the dashboard functionality. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class Dashboard { + private @Nullable ZonedDateTime timeUtc; + + @SerializedName("BoilerOn") + private int boilerOn; + + @SerializedName("BoilerOff") + private int boilerOff; + + @SerializedName("Temperature") + private double temperature; + + private TrendDescription pressureTrend = TrendDescription.UNKNOWN; + private TrendDescription tempTrend = TrendDescription.UNKNOWN; + private @Nullable ZonedDateTime dateMaxTemp; + private @Nullable ZonedDateTime dateMinTemp; + private double minTemp; + private double maxTemp; + @SerializedName("AbsolutePressure") + private double absolutePressure; + + @SerializedName("CO2") + private double co2; + + @SerializedName("Humidity") + private double humidity; + + @SerializedName("Noise") + private double noise; + + @SerializedName("Pressure") + private double pressure; + + @SerializedName("Rain") + private double rain; + @SerializedName("sum_rain_1") + private double sumRain1; + @SerializedName("sum_rain_24") + private double sumRain24; + + @SerializedName("WindAngle") + private int windAngle; + + @SerializedName("GustAngle") + private int gustAngle; + + @SerializedName("WindStrength") + private int windStrength; + + private int maxWindStr; + private @Nullable ZonedDateTime dateMaxWindStr; + + @SerializedName("GustStrength") + private int gustStrength; + + private int healthIdx; + + public @Nullable ZonedDateTime getTimeUtc() { + return timeUtc; + } + + public int getBoilerOn() { + return boilerOn; + } + + public int getBoilerOff() { + return boilerOff; + } + + public double getTemperature() { + return temperature; + } + + public TrendDescription getTempTrend() { + return tempTrend; + } + + public @Nullable ZonedDateTime getDateMaxTemp() { + return dateMaxTemp; + } + + public @Nullable ZonedDateTime getDateMinTemp() { + return dateMinTemp; + } + + public double getMinTemp() { + return minTemp; + } + + public double getMaxTemp() { + return maxTemp; + } + + public double getAbsolutePressure() { + return absolutePressure; + } + + public double getCo2() { + return co2; + } + + public double getHumidity() { + return humidity; + } + + public double getNoise() { + return noise; + } + + public double getPressure() { + return pressure; + } + + public TrendDescription getPressureTrend() { + return pressureTrend; + } + + public double getRain() { + return rain; + } + + public double getSumRain1() { + return sumRain1; + } + + public double getSumRain24() { + return sumRain24; + } + + public double getWindAngle() { + return windAngle; + } + + public double getGustAngle() { + return gustAngle; + } + + public double getWindStrength() { + return windStrength; + } + + public double getMaxWindStr() { + return maxWindStr; + } + + public @Nullable ZonedDateTime getDateMaxWindStr() { + return dateMaxWindStr; + } + + public double getGustStrength() { + return gustStrength; + } + + public int getHealthIdx() { + return healthIdx; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Device.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Device.java new file mode 100644 index 0000000000000..c01d3f91f9d2c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Device.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; + +/** + * The {@link Device} holds common data for all Netatmo devices. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class Device extends NAThing { + private @Nullable NAObjectMap modules; + private long dateSetup; + private long lastUpgrade; + private @Nullable Place place; + + public NAObjectMap getModules() { + NAObjectMap localModules = modules; + return localModules != null ? localModules : new NAObjectMap<>(); + } + + public long getDateSetup() { + return dateSetup; + } + + public long getLastUpgrade() { + return lastUpgrade; + } + + public Optional getPlace() { + return Optional.ofNullable(place); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Event.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Event.java new file mode 100644 index 0000000000000..305815c0c7c21 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Event.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.time.ZonedDateTime; +import java.util.Optional; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.EventSubType; +import org.openhab.binding.netatmo.internal.api.data.EventType; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link Event} holds information transferred by the webhook. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public abstract class Event extends NAObject { + protected EventType type = EventType.UNKNOWN; + @SerializedName(value = "camera_id", alternate = { "module_id" }) + private String cameraId = ""; + protected int subType = -1; + + public abstract ZonedDateTime getTime(); + + public abstract @Nullable String getSnapshotUrl(); + + public abstract @Nullable String getPersonId(); + + public EventType getEventType() { + return type; + } + + public String getCameraId() { + return cameraId; + } + + @Override + public @Nullable String getName() { + String localMessage = super.getName(); + return (localMessage != null ? localMessage.replace("", "").replace("", "") : ""); + } + + public Optional getSubTypeDescription() { + return Stream.of(EventSubType.values()).filter(v -> v.types.contains(getEventType()) && v.subType == subType) + .findFirst(); + } + + public void setEventType(EventType type) { + this.type = type; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Home.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Home.java new file mode 100644 index 0000000000000..371927d4f1875 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Home.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; + +/** + * The {@link Home} holds home information. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class Home extends Device implements Location { + private double[] coordinates = {}; + private double altitude; + private List events = List.of(); + + @Override + public ModuleType getType() { + return ModuleType.HOME; + } + + @Override + public double getAltitude() { + return altitude; + } + + @Override + public double[] getCoordinates() { + return coordinates; + } + + public List getEvents() { + return events; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeData.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeData.java new file mode 100644 index 0000000000000..2294fe45cd80c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeData.java @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.ApiResponse; +import org.openhab.binding.netatmo.internal.api.ListBodyResponse; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; + +/** + * The {@link HomeData} holds home information returned by homesdata endpoint. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class HomeData extends NAThing implements NAModule, LocationEx { + public class HomesDataResponse extends ApiResponse> { + } + + private double altitude; + private double[] coordinates = {}; + private @Nullable String country; + private @Nullable String timezone; + + private @Nullable String temperatureControlMode; + private SetpointMode thermMode = SetpointMode.UNKNOWN; + private int thermSetpointDefaultDuration; + private List schedules = List.of(); + + private NAObjectMap persons = new NAObjectMap<>(); + private NAObjectMap rooms = new NAObjectMap<>(); + private NAObjectMap modules = new NAObjectMap<>(); + + @Override + public ModuleType getType() { + return ModuleType.HOME; + } + + @Override + public double getAltitude() { + return altitude; + } + + @Override + public double[] getCoordinates() { + return coordinates; + } + + @Override + public Optional getCountry() { + return Optional.ofNullable(country); + } + + @Override + public Optional getTimezone() { + return Optional.ofNullable(timezone); + } + + public int getThermSetpointDefaultDuration() { + return thermSetpointDefaultDuration; + } + + public SetpointMode getThermMode() { + return thermMode; + } + + public NAObjectMap getPersons() { + return persons; + } + + public List getKnownPersons() { + return persons.values().stream().filter(HomeDataPerson::isKnown).collect(Collectors.toList()); + } + + public Optional getTemperatureControlMode() { + return Optional.ofNullable(temperatureControlMode); + } + + public NAObjectMap getRooms() { + return rooms; + } + + public NAObjectMap getModules() { + return modules; + } + + public Set getFeatures() { + return getModules().values().stream().map(m -> m.getType().feature).collect(Collectors.toSet()); + } + + public List getThermSchedules() { + return schedules; + } + + public @Nullable ThermProgram getActiveProgram() { + return schedules.stream().filter(ThermProgram::isSelected).findFirst().orElse(null); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataModule.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataModule.java new file mode 100644 index 0000000000000..da2c91b630d39 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataModule.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link HomeDataModule} holds module informations returned by getHomeData endpoint + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class HomeDataModule extends NAThing implements NAModule { + private @Nullable ZonedDateTime setupDate; + private @Nullable String applianceType; + private List moduleBridged = List.of(); + + public @Nullable String getApplianceType() { + return applianceType; + } + + public Optional getSetupDate() { + return Optional.ofNullable(setupDate); + } + + public List getModuleBridged() { + return moduleBridged; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataPerson.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataPerson.java new file mode 100644 index 0000000000000..278a22740561b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataPerson.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; + +/** + * The {@link HomeDataPerson} provides Person informations returned by getHomeData endpoint + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class HomeDataPerson extends NAThing implements NAModule { + private @Nullable String url; + + @Override + public ModuleType getType() { + return ModuleType.PERSON; + } + + public boolean isKnown() { + return description != null; + } + + public Optional getUrl() { + return Optional.ofNullable(url); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataRoom.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataRoom.java new file mode 100644 index 0000000000000..d89bea2aa3d73 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeDataRoom.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; + +/** + * The {@link HomeDataRoom} provides Room informations returned by getHomeData endpoint + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HomeDataRoom extends NAObject implements NAModule { + private List moduleIds = List.of(); + + @Override + public ModuleType getType() { + // In json api answer type for NARoom is used with free strings like kitchen, living... + return ModuleType.ROOM; + } + + public List getModuleIds() { + return moduleIds; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeEvent.java new file mode 100644 index 0000000000000..6a56c2cbc4a12 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeEvent.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.ApiResponse; +import org.openhab.binding.netatmo.internal.api.BodyResponse; +import org.openhab.binding.netatmo.internal.api.data.EventSubType; +import org.openhab.binding.netatmo.internal.api.data.EventType; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.EventCategory; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.VideoStatus; + +/** + * The {@link HomeEvent} holds information transferred by the webhook about a home event. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class HomeEvent extends Event { + public class NAEventsDataResponse extends ApiResponse> { + } + + private ZonedDateTime time = ZonedDateTime.now(); + private @Nullable String personId; + private EventCategory category = EventCategory.UNKNOWN; + private @Nullable Snapshot snapshot; + private @Nullable String videoId; + private VideoStatus videoStatus = VideoStatus.UNKNOWN; + private boolean isArrival; + + @Override + public ZonedDateTime getTime() { + return time; + } + + @Override + public @Nullable String getPersonId() { + return personId; + } + + public @Nullable String getVideoId() { + return videoId; + } + + public VideoStatus getVideoStatus() { + return videoStatus; + } + + @Override + public Optional getSubTypeDescription() { + // Blend extra information provided by this kind of event in subcategories... + if (type == EventType.PERSON) { + subType = isArrival ? EventSubType.PERSON_ARRIVAL.subType : EventSubType.PERSON_SEEN.subType; + } else if (type == EventType.PERSON_HOME) { + subType = EventSubType.PERSON_ARRIVAL.subType; + } else if (type == EventType.PERSON_AWAY) { + subType = EventSubType.PERSON_DEPARTURE.subType; + } else if (type == EventType.HUMAN) { + subType = EventSubType.MOVEMENT_HUMAN.subType; + } else if (type == EventType.ANIMAL) { + subType = EventSubType.MOVEMENT_ANIMAL.subType; + } else { + if (category == EventCategory.ANIMAL) { + subType = EventSubType.MOVEMENT_ANIMAL.subType; + } else if (category == EventCategory.HUMAN) { + subType = EventSubType.MOVEMENT_HUMAN.subType; + } else if (category == EventCategory.VEHICLE) { + subType = EventSubType.MOVEMENT_VEHICLE.subType; + } + } + // ... and let ancestor do his work + return super.getSubTypeDescription(); + } + + @Override + public @Nullable String getSnapshotUrl() { + Snapshot localSnap = snapshot; + return localSnap != null ? localSnap.getUrl() : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusModule.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusModule.java new file mode 100644 index 0000000000000..ac53c2a34eadb --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusModule.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.AlimentationStatus; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.BatteryState; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SdCardStatus; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link HomeStatusModule} holds module informations returned by getHomeData endpoint + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class HomeStatusModule extends NAThing { + private @Nullable String firmwareName; + private @Nullable String wifiState; + private @Nullable String status; + private @Nullable OnOffType monitoring; + private FloodLightMode floodlight = FloodLightMode.UNKNOWN; + private SdCardStatus sdStatus = SdCardStatus.UNKNOWN; + private AlimentationStatus alimStatus = AlimentationStatus.UNKNOWN; + private @Nullable String sirenStatus; + private @Nullable String vpnUrl; + private boolean isLocal; + private BatteryState batteryState = BatteryState.UNKNOWN; + private int batteryLevel; + + private @Nullable OpenClosedType boilerStatus; + private boolean boilerValveComfortBoost; + + public State getBoilerStatus() { + OpenClosedType status = boilerStatus; + return status != null ? status : UnDefType.NULL; + } + + public boolean getBoilerValveComfortBoost() { + return boilerValveComfortBoost; + } + + public Optional getFirmwareName() { + return Optional.ofNullable(firmwareName); + } + + public Optional getWifiState() { + return Optional.ofNullable(wifiState); + } + + public Optional getStatus() { + return Optional.ofNullable(status); + } + + public State getMonitoring() { + OnOffType localStatus = monitoring; + return localStatus != null ? localStatus : UnDefType.NULL; + } + + public FloodLightMode getFloodlight() { + return floodlight; + } + + public SdCardStatus getSdStatus() { + return sdStatus; + } + + public AlimentationStatus getAlimStatus() { + return alimStatus; + } + + public Optional getSirenStatus() { + return Optional.ofNullable(sirenStatus); + } + + public @Nullable String getVpnUrl() { + return vpnUrl; + } + + public boolean isLocal() { + return isLocal; + } + + public BatteryState getBatteryState() { + return batteryState; + } + + public int getBatteryLevel() { + return batteryLevel; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusPerson.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusPerson.java new file mode 100644 index 0000000000000..260a6fd19cdec --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusPerson.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; + +/** + * The {@link HomeStatusPerson} provides Person informations returned by getHomeData endpoint + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class HomeStatusPerson extends NAThing { + private boolean outOfSight; + + @Override + public ModuleType getType() { + return ModuleType.PERSON; + } + + public boolean isOutOfSight() { + return outOfSight; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Location.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Location.java new file mode 100644 index 0000000000000..ae4fb347acce3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Location.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.PointType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link Location} is the common interface for dto holding a location + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public interface Location { + double[] getCoordinates(); + + double getAltitude(); + + default State getLocation() { + double[] coordinates = getCoordinates(); + return coordinates.length != 2 ? UnDefType.UNDEF + : new PointType(new DecimalType(coordinates[1]), new DecimalType(coordinates[0]), + new DecimalType(getAltitude())); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/LocationEx.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/LocationEx.java new file mode 100644 index 0000000000000..d4bf05f126c3d --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/LocationEx.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LocationEx} is the common interface for dto holding a extra location data + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public interface LocationEx extends Location { + public Optional getCountry(); + + public Optional getTimezone(); +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/MeasureBodyElem.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/MeasureBodyElem.java new file mode 100644 index 0000000000000..8dd8d8358cefd --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/MeasureBodyElem.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link MeasureBodyElem} holds a list of values returned by getMeasure endpoint. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class MeasureBodyElem { + private List> value = List.of(); + + public List> getValue() { + return value; + } + + public @Nullable T getSingleValue() { + if (!value.isEmpty()) { + List first = value.get(0); + if (!first.isEmpty()) { + return first.get(0); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Module.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Module.java new file mode 100644 index 0000000000000..3e3ca3ebe8187 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Module.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.BatteryState; + +/** + * The {@link Module} holds status information of a Netatmo module. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class Module extends NAThing { + private BatteryState batteryState = BatteryState.UNKNOWN; + private int batteryPercent = -1; + + public int getBatteryPercent() { + return batteryPercent != -1 ? batteryPercent : batteryState.level; + } + + public BatteryState getBatteryState() { + return batteryState; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java new file mode 100644 index 0000000000000..1960abed12aef --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.ApiResponse; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; + +/** + * The {@link NAHomeStatus} holds data for a given home. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class NAHomeStatus { + public class NAHomeStatusResponse extends ApiResponse { + } + + public class HomeStatus extends NAThing { + private @Nullable NAObjectMap modules; + private @Nullable NAObjectMap persons; + private @Nullable NAObjectMap rooms; + + public NAObjectMap getModules() { + NAObjectMap localModules = modules; + return localModules != null ? localModules : new NAObjectMap<>(); + } + + public NAObjectMap getPersons() { + NAObjectMap localPersons = persons; + return localPersons != null ? localPersons : new NAObjectMap<>(); + } + + public NAObjectMap getRooms() { + NAObjectMap localRooms = rooms; + return localRooms != null ? localRooms : new NAObjectMap<>(); + } + } + + private @Nullable HomeStatus home; + + public Optional getHomeStatus() { + return Optional.ofNullable(home); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMain.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMain.java new file mode 100644 index 0000000000000..6d64fa26e4417 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAMain.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.ApiResponse; +import org.openhab.binding.netatmo.internal.api.ListBodyResponse; + +/** + * The {@link NAMain} defines a weather or nhc device. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAMain extends Device { + public class StationDataResponse extends ApiResponse> { + } + + private boolean readOnly; + + /** + * true when the user was invited to (or has favorited) a station, false when the user owns it + * + * @return readOnly + **/ + public boolean isReadOnly() { + return readOnly; + } + + public boolean hasFreshData(int dataFreshnessLimit) { + // check by comparing data freshness + ZonedDateTime localLastSeen = lastSeen; + if (localLastSeen != null && !getType().isLogical()) { + return Duration.between(localLastSeen.toInstant(), Instant.now()).getSeconds() < dataFreshnessLimit; + } + return true; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAModule.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAModule.java new file mode 100644 index 0000000000000..a4aea48df2b1a --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAModule.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; + +/** + * The {@link NAModule} is the common interface for dto holding module informations + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public interface NAModule { + public String getId(); + + public @Nullable String getName(); + + public ModuleType getType(); +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAObject.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAObject.java new file mode 100644 index 0000000000000..9ce46a836d3af --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAObject.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link NAObject} class is the base class for all objects + * returned by the Netatmo API. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class NAObject { + @SerializedName(value = "id", alternate = { "program_id", "_id", "event_id" }) + protected String id = ""; + @SerializedName(value = "name", alternate = { "module_name", "station_name", "pseudo", "message", "key" }) + protected @Nullable String description; + private boolean ignoredForThingUpdate; + + public String getId() { + return id; + } + + public @Nullable String getName() { + return description; + } + + public boolean isIgnoredForThingUpdate() { + return ignoredForThingUpdate; + } + + public void setIgnoredForThingUpdate(boolean ignoredForThingUpdate) { + this.ignoredForThingUpdate = ignoredForThingUpdate; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThing.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThing.java new file mode 100644 index 0000000000000..7f0af823bbd5e --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAThing.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link NAThing} is the base class for devices and modules. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class NAThing extends NAObject implements NAModule { + @SerializedName(value = "rf_status", alternate = { "wifi_status", "rf_strength", "wifi_strength" }) + private int radioStatus = -1; + @SerializedName(value = "last_seen", alternate = { "last_therm_seen", "last_status_store", "last_plug_seen", + "last_message", "last_activity" }) + protected @Nullable ZonedDateTime lastSeen; + @SerializedName(value = "firmware", alternate = { "firmware_revision" }) + private @Nullable String firmware; + private @Nullable Boolean reachable; + private @Nullable Dashboard dashboardData; + + private @Nullable String roomId; + private @Nullable String bridge; + private ModuleType type = ModuleType.UNKNOWN; + + @Override + public ModuleType getType() { + return type; + } + + public boolean isReachable() { + // This is not implemented on all devices/modules, so if absent we consider it is reachable + Boolean localReachable = this.reachable; + return localReachable != null ? localReachable : true; + } + + public void setReachable(boolean reachable) { + this.reachable = reachable; + } + + public @Nullable Dashboard getDashboardData() { + return dashboardData; + } + + public @Nullable String getFirmware() { + return firmware; + } + + public int getRadioStatus() { + return radioStatus; + } + + public Optional getLastSeen() { + return Optional.ofNullable(lastSeen); + } + + /** + * @return true if the equipment has no parent, meaning its a device. + */ + public boolean isDevice() { + return bridge == null; + } + + public @Nullable String getBridge() { + return bridge; + } + + public @Nullable String getRoomId() { + return roomId; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Person.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Person.java new file mode 100644 index 0000000000000..788529a109b2b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Person.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; + +/** + * The {@link Person} holds answers provided in webhook events + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class Person extends NAThing { + private @Nullable String faceUrl; + private boolean isKnown; + + @Override + public ModuleType getType() { + return ModuleType.PERSON; + } + + public @Nullable String getFaceUrl() { + return faceUrl; + } + + public boolean isKnown() { + return isKnown; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Ping.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Ping.java new file mode 100644 index 0000000000000..46efb6f03faaf --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Ping.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.ApiResponse; + +/** + * The {@link Ping} hold url data for a camera module + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class Ping extends ApiResponse { + private String localUrl = ""; + private @Nullable String productName; + + @Override + public String getStatus() { + return localUrl; + } + + @Override + public @Nullable String getBody() { + return productName; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Place.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Place.java new file mode 100644 index 0000000000000..6b774594dfa88 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Place.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link Place} reports location information of a Netatmo system. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class Place implements LocationEx { + private @Nullable String city; + private @Nullable String country; + private @Nullable String timezone; + private double altitude; + private double[] location = {}; + + public Optional getCity() { + return Optional.ofNullable(city); + } + + @Override + public Optional getCountry() { + return Optional.ofNullable(country); + } + + @Override + public Optional getTimezone() { + return Optional.ofNullable(timezone); + } + + @Override + public double getAltitude() { + return altitude; + } + + @Override + public double[] getCoordinates() { + return location; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Room.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Room.java new file mode 100644 index 0000000000000..783252499b10a --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Room.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link Room} holds temperature data for a given room. + * + * @author Bernhard Kreuz - Initial contribution + * + */ +@NonNullByDefault +public class Room extends NAObject implements NAModule { + private @Nullable String type; + private @Nullable OnOffType anticipating; + private boolean openWindow; + private @Nullable ZonedDateTime thermSetpointStartTime; + private @Nullable ZonedDateTime thermSetpointEndTime; + private SetpointMode thermSetpointMode = SetpointMode.UNKNOWN; + private int heatingPowerRequest; + private double thermMeasuredTemperature; + private double thermSetpointTemperature; + + public State isAnticipating() { + OnOffType status = anticipating; + return status != null ? status : UnDefType.NULL; + } + + public State hasOpenedWindows() { + return openWindow ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + } + + public int getHeatingPowerRequest() { + return heatingPowerRequest; + } + + public double getMeasuredTemp() { + return thermMeasuredTemperature; + } + + public SetpointMode getSetpointMode() { + return thermSetpointMode; + } + + public double getSetpointTemp() { + return thermSetpointTemperature; + } + + public @Nullable ZonedDateTime getSetpointBegin() { + return thermSetpointStartTime; + } + + public @Nullable ZonedDateTime getSetpointEnd() { + return thermSetpointEndTime; + } + + @Override + public ModuleType getType() { + // Note: In json api answer type for NARoom is used with words like kitchen, living... + return ModuleType.ROOM; + } + + public @Nullable String getLocation() { + return type; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Snapshot.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Snapshot.java new file mode 100644 index 0000000000000..54394a6d3bb4d --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Snapshot.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link Snapshot} holds data related to a snapshot. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class Snapshot { + private @Nullable String url; + + public @Nullable String getUrl() { + return url; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/ThermProgram.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/ThermProgram.java new file mode 100644 index 0000000000000..b9a80518eaeb6 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/ThermProgram.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; + +/** + * The {@link ThermProgram} holds setpoint scheduling information. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class ThermProgram extends NAObject { + private NAObjectMap zones = new NAObjectMap<>(); + private List timetable = List.of(); + private boolean selected; + + public List getTimetable() { + return timetable; + } + + public boolean isSelected() { + return selected; + } + + public @Nullable Zone getZone(String id) { + return zones.get(id); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/TimeTableItem.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/TimeTableItem.java new file mode 100644 index 0000000000000..22f63cd462989 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/TimeTableItem.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link TimeTableItem} holds the temp scheduling for a given zone. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class TimeTableItem extends NAObject { + private int mOffset; + private int zoneId; + + public int getMinuteOffset() { + return mOffset; + } + + public int getZoneId() { + return zoneId; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java new file mode 100644 index 0000000000000..eefa6a53815b1 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.EventType; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.binding.netatmo.internal.deserialization.NAPushType; + +/** + * The {@link WebhookEvent} is responsible to hold + * data given back by the Netatmo API when calling the webhook + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class WebhookEvent extends Event { + private @NonNullByDefault({}) NAPushType pushType; + private String homeId = ""; + private @Nullable String snapshotUrl; + private NAObjectMap 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() { + return persons.size() > 0 ? persons.keySet().iterator().next() : null; + } + + @Override + public @Nullable String getSnapshotUrl() { + return snapshotUrl; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Zone.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Zone.java new file mode 100644 index 0000000000000..ad68358df50c1 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/Zone.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ThermostatZoneType; + +/** + * The {@link Zone} holds temperature data for a given zone. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class Zone extends NAObject { + private ThermostatZoneType type = ThermostatZoneType.UNKNOWN; + private double temp; + + public double getTemp() { + return temp; + } + + public ThermostatZoneType getType() { + return type; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraAddress.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraAddress.java deleted file mode 100644 index 1a94ecaa7e3b9..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraAddress.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2010-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.camera; - -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * {@link CameraAddress} handles the data to address a camera (VPN and local address). - * - * @author Sven Strohschein - Initial contribution - */ -@NonNullByDefault -public class CameraAddress { - - private final String vpnURL; - private final String localURL; - - CameraAddress(final String vpnURL, final String localURL) { - this.vpnURL = vpnURL; - this.localURL = localURL; - } - - public String getVpnURL() { - return vpnURL; - } - - public String getLocalURL() { - return localURL; - } - - /** - * Checks if the VPN URL was changed / isn't equal to the given VPN-URL. - * - * @param vpnURL old / known VPN URL - * @return true, when the VPN URL isn't equal given VPN URL, otherwise false - */ - public boolean isVpnURLChanged(String vpnURL) { - return !getVpnURL().equals(vpnURL); - } - - @Override - public boolean equals(@Nullable Object object) { - if (this == object) { - return true; - } - if (object == null || getClass() != object.getClass()) { - return false; - } - CameraAddress that = (CameraAddress) object; - return vpnURL.equals(that.vpnURL) && localURL.equals(that.localURL); - } - - @Override - public int hashCode() { - return Objects.hash(vpnURL, localURL); - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/camera/CameraHandler.java deleted file mode 100644 index 8fda400ce1528..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.camera; - -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.io.IOException; -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.json.JSONException; -import org.json.JSONObject; -import org.openhab.binding.netatmo.internal.ChannelTypeUtils; -import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.io.net.http.HttpUtil; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.swagger.client.model.NAWelcomeCamera; - -/** - * {@link CameraHandler} is the class used to handle Camera Data - * - * @author Sven Strohschein - Initial contribution (partly moved code from NAWelcomeCameraHandler to introduce - * inheritance, see NAWelcomeCameraHandler) - * - */ -@NonNullByDefault -public abstract class CameraHandler extends NetatmoModuleHandler { - - 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/BatteryHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/BatteryHelper.java deleted file mode 100644 index a12bdf9311663..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/BatteryHelper.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (c) 2010-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.channelhelper; - -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.ChannelTypeUtils; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link BatteryHelper} handle specific behavior - * of modules using batteries - * - * @author Gaël L'hopital - Initial contribution - * - */ -@NonNullByDefault -public class BatteryHelper { - private final Logger logger = LoggerFactory.getLogger(BatteryHelper.class); - private int batteryLow; - - private @Nullable Object module; - - public BatteryHelper(String batteryLevels) { - List thresholds = Arrays.asList(batteryLevels.split(",")); - batteryLow = Integer.parseInt(thresholds.get(1)); - } - - public void setModule(Object module) { - this.module = module; - } - - 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); - } - } - return Optional.empty(); - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RadioHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/channelhelper/RadioHelper.java deleted file mode 100644 index 8fb0305d2f8c7..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.channelhelper; - -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link RadioHelper} handle specific behavior - * of WIFI or RF devices and modules - * - * @author Gaël L'hopital - Initial contribution - * - */ -@NonNullByDefault -public class RadioHelper { - private final Logger logger = LoggerFactory.getLogger(RadioHelper.class); - private final List 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/config/ApiHandlerConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/ApiHandlerConfiguration.java new file mode 100644 index 0000000000000..782d04c06879c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/ApiHandlerConfiguration.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoException; + +/** + * The {@link ApiHandlerConfiguration} is responsible for holding configuration + * information needed to access Netatmo API and general binding behavior setup + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class ApiHandlerConfiguration { + public class Credentials { + public final String clientId, clientSecret, username, password; + + private Credentials(@Nullable String clientId, @Nullable String clientSecret, @Nullable String username, + @Nullable String password) throws NetatmoException { + this.clientSecret = checkMandatory(clientSecret, "@text/conf-error-no-client-secret"); + this.username = checkMandatory(username, "@text/conf-error-no-username"); + this.password = checkMandatory(password, "@text/conf-error-no-password"); + this.clientId = checkMandatory(clientId, "@text/conf-error-no-client-id"); + } + + private String checkMandatory(@Nullable String value, String error) throws NetatmoException { + if (value == null || value.isBlank()) { + throw new NetatmoException(error); + } + return value; + } + + @Override + public String toString() { + return "Credentials [clientId=" + clientId + ", username=" + username + + ", password=******, clientSecret=******]"; + } + } + + private @Nullable String clientId; + private @Nullable String clientSecret; + private @Nullable String username; + private @Nullable String password; + public @Nullable String webHookUrl; + public int reconnectInterval = 300; + + public Credentials getCredentials() throws NetatmoException { + return new Credentials(clientId, clientSecret, username, password); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/BindingConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/BindingConfiguration.java new file mode 100644 index 0000000000000..b52ec31a9d05b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/BindingConfiguration.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.config; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; + +/** + * The {@link BindingConfiguration} is responsible for holding configuration of the binding itself. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class BindingConfiguration { + public Set features = Set.of(); + public boolean readFriends = false; + + public void update(BindingConfiguration newConfig) { + this.features = newConfig.features; + this.readFriends = newConfig.readFriends; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/MeasureConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/MeasureConfiguration.java new file mode 100644 index 0000000000000..753793e840d7a --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/MeasureConfiguration.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link MeasureConfiguration} is responsible for holding + * configuration information for measure channels + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class MeasureConfiguration { + public String period = ""; + public String limit = ""; +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NAThingConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NAThingConfiguration.java new file mode 100644 index 0000000000000..621dac474faa5 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NAThingConfiguration.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link NAThingConfiguration} is responsible for holding + * configuration information for any Netatmo thing module or device + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NAThingConfiguration { + public String id = ""; + public int refreshInterval = -1; +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBridgeConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/NetatmoBridgeConfiguration.java deleted file mode 100644 index 15bad93f75de5..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.config; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link NetatmoBridgeConfiguration} is responsible for holding - * configuration informations needed to access Netatmo API - * - * @author Gaël L'hopital - Initial contribution - */ -@NonNullByDefault -public class NetatmoBridgeConfiguration { - public @Nullable String clientId; - public @Nullable String clientSecret; - public @Nullable String username; - public @Nullable String password; - public boolean readStation = true; - public boolean readThermostat = false; - public boolean readHealthyHomeCoach = false; - public boolean readWelcome = false; - public boolean readPresence = false; - public @Nullable String webHookUrl; - public int reconnectInterval = 5400; -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADeserializer.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADeserializer.java new file mode 100644 index 0000000000000..1976cf7f88cf2 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADeserializer.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.deserialization; + +import java.time.Instant; +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.OpenClosedType; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link NADeserializer} is responsible to instantiate suitable Gson (de)serializer + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +@Component(service = NADeserializer.class) +public class NADeserializer { + private final Gson gson; + + @Activate + public NADeserializer(@Reference TimeZoneProvider timeZoneProvider) { + gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()) + .registerTypeAdapter(NAObjectMap.class, new NAObjectMapDeserializer()) + .registerTypeAdapter(NAPushType.class, new NAPushTypeDeserializer()) + .registerTypeAdapter(ZonedDateTime.class, + (JsonDeserializer) (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(); + } + + public T deserialize(Class clazz, String json) throws NetatmoException { + try { + @Nullable + T result = gson.fromJson(json, clazz); + if (result != null) { + return result; + } + throw new NetatmoException("Deserialization of '%s' resulted in null value", json); + } catch (JsonSyntaxException e) { + throw new NetatmoException(e, "Unexpected error deserializing '%s'", json); + } + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java new file mode 100644 index 0000000000000..dc5f64c245afd --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMap.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.deserialization; + +import java.util.HashMap; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; + +/** + * The {@link NAObjectMap} defines a hashmap of NAObjects identified by their id. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NAObjectMap extends HashMap { + private static final long serialVersionUID = 7635233672795516649L; + + @Nullable + public T put(T thing) { + return super.put(thing.getId(), thing); + } + + public Optional getOpt(String key) { + return Optional.ofNullable(super.get(key)); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMapDeserializer.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMapDeserializer.java new file mode 100644 index 0000000000000..a92bdbd4bf075 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAObjectMapDeserializer.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.deserialization; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +/** + * The {@link NAObjectMapDeserializer} is a specialized deserializer aimed to transform + * a list of `NAObjects` into a map identified by the object's id. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +class NAObjectMapDeserializer implements JsonDeserializer> { + @Override + public @Nullable NAObjectMap deserialize(JsonElement json, Type clazz, JsonDeserializationContext context) + throws JsonParseException { + ParameterizedType parameterized = (ParameterizedType) clazz; + Type[] typeArguments = parameterized.getActualTypeArguments(); + if (typeArguments.length > 0 && json instanceof JsonArray) { + Type objectType = typeArguments[0]; + NAObjectMap result = new NAObjectMap<>(); + ((JsonArray) json).forEach(item -> { + result.put(context.deserialize(item, objectType)); + }); + return result; + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushType.java new file mode 100644 index 0000000000000..096e8943b5661 --- /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-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.deserialization; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.EventType; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; + +/** + * This class holds data of push_type field + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NAPushType { + private final ModuleType moduleType; + private final EventType event; + + NAPushType(ModuleType moduleType, EventType event) { + this.moduleType = moduleType; + this.event = event; + } + + public ModuleType getModuleType() { + return moduleType; + } + + public EventType getEvent() { + return event; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushTypeDeserializer.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushTypeDeserializer.java new file mode 100644 index 0000000000000..b99cb4db68c92 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushTypeDeserializer.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.deserialization; + +import java.lang.reflect.Type; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.EventType; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +/** + * Specialized deserializer for push_type field + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +class NAPushTypeDeserializer implements JsonDeserializer { + + @Override + public @Nullable NAPushType deserialize(JsonElement json, Type clazz, JsonDeserializationContext context) + throws JsonParseException { + String string = json.getAsString(); + String[] elements = string.split("-"); + if (elements.length > 1) { + try { + ModuleType moduleType = ModuleType.from(elements[0]); + EventType eventType = EventType.valueOf(elements[1].toUpperCase()); + + return new NAPushType(moduleType, eventType); + } catch (IllegalArgumentException e) { + } + } + throw new JsonParseException("Error deserializing : " + string); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/StrictEnumTypeAdapterFactory.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/StrictEnumTypeAdapterFactory.java new file mode 100644 index 0000000000000..2fd5d3cade000 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/StrictEnumTypeAdapterFactory.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.deserialization; + +import java.io.IOException; +import java.io.StringReader; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * Enforces a fallback to UNKNOWN when deserializing enum types, marked as @NonNull whereas they were valued + * to null if the appropriate value is absent. It will give more resilience to the binding when Netatmo API evolves. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +class StrictEnumTypeAdapterFactory implements TypeAdapterFactory { + private static final StringReader UNKNOWN = new StringReader("\"UNKNOWN\""); + + @Override + public @Nullable TypeAdapter create(@NonNullByDefault({}) Gson gson, + @NonNullByDefault({}) TypeToken type) { + @SuppressWarnings("unchecked") + Class rawType = (Class) type.getRawType(); + return rawType.isEnum() ? newStrictEnumAdapter(gson.getDelegateAdapter(this, type)) : null; + } + + private TypeAdapter newStrictEnumAdapter(@NonNullByDefault({}) 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 { + JsonReader delegateReader = new JsonReader(new StringReader('"' + in.nextString() + '"')); + @Nullable + T value = delegateAdapter.read(delegateReader); + delegateReader.close(); + if (value == null) { + UNKNOWN.reset(); + value = delegateAdapter.read(new JsonReader(UNKNOWN)); + } + return value; + } + }; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoDiscoveryService.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoDiscoveryService.java new file mode 100644 index 0000000000000..e1ec6ff247ef4 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoDiscoveryService.java @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.discovery; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.EQUIPMENT_ID; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.AircareApi; +import org.openhab.binding.netatmo.internal.api.HomeApi; +import org.openhab.binding.netatmo.internal.api.ListBodyResponse; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.WeatherApi; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.api.dto.NAMain; +import org.openhab.binding.netatmo.internal.api.dto.NAModule; +import org.openhab.binding.netatmo.internal.config.BindingConfiguration; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link NetatmoDiscoveryService} searches for available Netatmo things + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class NetatmoDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService, DiscoveryService { + private static final Set SKIPPED_TYPES = Set.of(ModuleType.UNKNOWN, ModuleType.ACCOUNT); + private static final int DISCOVER_TIMEOUT_SECONDS = 5; + private final Logger logger = LoggerFactory.getLogger(NetatmoDiscoveryService.class); + private @Nullable ApiBridgeHandler handler; + private @Nullable BindingConfiguration config; + + public NetatmoDiscoveryService() { + super(ModuleType.AS_SET.stream().filter(mt -> !SKIPPED_TYPES.contains(mt)).map(mt -> mt.thingTypeUID) + .collect(Collectors.toSet()), DISCOVER_TIMEOUT_SECONDS); + } + + @Override + public void startScan() { + BindingConfiguration localConf = config; + ApiBridgeHandler localHandler = handler; + if (localHandler != null && localConf != null) { + ThingUID apiBridgeUID = localHandler.getThing().getUID(); + try { + AircareApi airCareApi = localHandler.getRestManager(AircareApi.class); + if (airCareApi != null) { // Search Healthy Home Coaches + ListBodyResponse body = airCareApi.getHomeCoachData(null).getBody(); + if (body != null) { + body.getElements().stream().forEach(homeCoach -> createThing(homeCoach, apiBridgeUID)); + } + } + if (localConf.readFriends) { + WeatherApi weatherApi = localHandler.getRestManager(WeatherApi.class); + if (weatherApi != null) { // Search favorite stations + ListBodyResponse body = weatherApi.getStationsData(null, true).getBody(); + if (body != null) { + body.getElements().stream().filter(NAMain::isReadOnly).forEach(station -> { + ThingUID bridgeUID = createThing(station, apiBridgeUID); + station.getModules().values().stream() + .forEach(module -> createThing(module, bridgeUID)); + }); + } + } + } + HomeApi homeApi = localHandler.getRestManager(HomeApi.class); + if (homeApi != null) { // Search all the rest + homeApi.getHomesData(null, null).stream().filter(h -> !h.getFeatures().isEmpty()).forEach(home -> { + ThingUID homeUID = createThing(home, apiBridgeUID); + home.getKnownPersons().forEach(person -> createThing(person, homeUID)); + home.getModules().values().stream().forEach(device -> { + ModuleType deviceType = device.getType(); + String deviceBridge = device.getBridge(); + ThingUID bridgeUID = deviceBridge != null && deviceType.getBridge() != ModuleType.HOME + ? findThingUID(deviceType.getBridge(), deviceBridge, apiBridgeUID) + : deviceType.getBridge() == ModuleType.HOME ? homeUID : apiBridgeUID; + createThing(device, bridgeUID); + }); + home.getRooms().values().stream().forEach(room -> { + room.getModuleIds().stream().map(id -> home.getModules().get(id)) + .map(m -> m != null ? m.getType().feature : FeatureArea.NONE) + .filter(f -> FeatureArea.ENERGY.equals(f)).findAny() + .ifPresent(f -> createThing(room, homeUID)); + }); + }); + } + } catch (NetatmoException e) { + logger.warn("Error during discovery process : {}", e.getMessage()); + } + } + } + + private ThingUID findThingUID(ModuleType thingType, String thingId, @Nullable ThingUID brigdeUID) { + for (ThingTypeUID supported : getSupportedThingTypes()) { + ThingTypeUID thingTypeUID = thingType.thingTypeUID; + if (supported.equals(thingTypeUID)) { + String id = thingId.replaceAll("[^a-zA-Z0-9_]", ""); + return brigdeUID == null ? new ThingUID(supported, id) : new ThingUID(supported, brigdeUID, id); + } + } + throw new IllegalArgumentException("Unsupported device type discovered : " + thingType); + } + + private ThingUID createThing(NAModule module, @Nullable ThingUID bridgeUID) { + ThingUID moduleUID = findThingUID(module.getType(), module.getId(), bridgeUID); + DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(moduleUID) + .withProperty(EQUIPMENT_ID, module.getId()).withRepresentationProperty(EQUIPMENT_ID) + .withLabel(module.getName() != null ? module.getName() : module.getId()); + if (bridgeUID != null) { + resultBuilder.withBridge(bridgeUID); + } + thingDiscovered(resultBuilder.build()); + return moduleUID; + } + + @Override + public void setThingHandler(ThingHandler handler) { + if (handler instanceof ApiBridgeHandler) { + this.handler = (ApiBridgeHandler) handler; + this.config = ((ApiBridgeHandler) handler).getConfiguration(); + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return handler; + } + + @Override + public void deactivate() { + super.deactivate(); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryService.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryService.java deleted file mode 100644 index 7b43671dbbe96..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.discovery; - -import static org.openhab.binding.netatmo.internal.APIUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler; -import org.openhab.binding.netatmo.internal.handler.NetatmoDataListener; -import org.openhab.core.config.discovery.AbstractDiscoveryService; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.i18n.LocaleProvider; -import org.openhab.core.i18n.TranslationProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkUtil; - -import io.swagger.client.model.NAHealthyHomeCoach; -import io.swagger.client.model.NAMain; -import io.swagger.client.model.NAPlug; -import io.swagger.client.model.NAStationModule; -import io.swagger.client.model.NAWelcomeCamera; -import io.swagger.client.model.NAWelcomeHome; - -/** - * The {@link NetatmoModuleDiscoveryService} searches for available Netatmo - * devices and modules connected to the API console - * - * @author Gaël L'hopital - Initial contribution - * @author Ing. Peter Weiss - Welcome camera implementation - * - */ -@NonNullByDefault -public class NetatmoModuleDiscoveryService extends AbstractDiscoveryService implements NetatmoDataListener { - private static final int SEARCH_TIME = 5; - private final NetatmoBridgeHandler netatmoBridgeHandler; - - public NetatmoModuleDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler, LocaleProvider localeProvider, - TranslationProvider translationProvider) { - super(SUPPORTED_DEVICE_THING_TYPES_UIDS, SEARCH_TIME); - this.netatmoBridgeHandler = netatmoBridgeHandler; - this.localeProvider = localeProvider; - this.i18nProvider = translationProvider; - } - - @Override - public void activate(@Nullable Map 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 c1c2974280edf..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.handler; - -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; -import static org.openhab.core.library.unit.MetricPrefix.*; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import javax.measure.Unit; -import javax.measure.quantity.Angle; -import javax.measure.quantity.Dimensionless; -import javax.measure.quantity.Length; -import javax.measure.quantity.Pressure; -import javax.measure.quantity.Speed; -import javax.measure.quantity.Temperature; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.channelhelper.BatteryHelper; -import org.openhab.binding.netatmo.internal.channelhelper.RadioHelper; -import org.openhab.core.config.core.Configuration; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.library.unit.Units; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.thing.ThingStatusInfo; -import org.openhab.core.thing.binding.BaseThingHandler; -import org.openhab.core.thing.binding.BridgeHandler; -import org.openhab.core.thing.type.ChannelKind; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * {@link AbstractNetatmoThingHandler} is the abstract class that handles - * common behaviors of all netatmo things - * - * @author Gaël L'hopital - Initial contribution OH2 version - * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules - * - */ -@NonNullByDefault -public abstract class AbstractNetatmoThingHandler extends BaseThingHandler { - // Units of measurement of the data delivered by the API - public static final Unit 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/handler/ApiBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java new file mode 100644 index 0000000000000..e0d7fe5c1a6d3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.InputStreamContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpStatus.Code; +import org.openhab.binding.netatmo.internal.api.ApiError; +import org.openhab.binding.netatmo.internal.api.AuthenticationApi; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.RestManager; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope; +import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration; +import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration.Credentials; +import org.openhab.binding.netatmo.internal.config.BindingConfiguration; +import org.openhab.binding.netatmo.internal.deserialization.NADeserializer; +import org.openhab.binding.netatmo.internal.discovery.NetatmoDiscoveryService; +import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.osgi.service.http.HttpService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link ApiBridgeHandler} is the handler for a Netatmo API and connects it to the framework. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class ApiBridgeHandler extends BaseBridgeHandler { + private static final int TIMEOUT_S = 20; + + private final Logger logger = LoggerFactory.getLogger(ApiBridgeHandler.class); + private final BindingConfiguration bindingConf; + private final HttpService httpService; + private final AuthenticationApi connectApi; + private final HttpClient httpClient; + private final NADeserializer deserializer; + + private Optional> connectJob = Optional.empty(); + private Optional servlet = Optional.empty(); + private @NonNullByDefault({}) ApiHandlerConfiguration thingConf; + + private Map, RestManager> managers = new HashMap<>(); + + public ApiBridgeHandler(Bridge bridge, HttpClient httpClient, HttpService httpService, NADeserializer deserializer, + BindingConfiguration configuration) { + super(bridge); + this.bindingConf = configuration; + this.httpService = httpService; + this.connectApi = new AuthenticationApi(this, scheduler); + this.httpClient = httpClient; + this.deserializer = deserializer; + } + + @Override + public void initialize() { + logger.debug("Initializing Netatmo API bridge handler."); + thingConf = getConfigAs(ApiHandlerConfiguration.class); + updateStatus(ThingStatus.UNKNOWN); + scheduler.execute(() -> { + openConnection(); + String webHookUrl = thingConf.webHookUrl; + if (webHookUrl != null && !webHookUrl.isBlank()) { + servlet = Optional.of(new NetatmoServlet(httpService, this, webHookUrl)); + } + }); + } + + private void openConnection() { + try { + Credentials credentials = thingConf.getCredentials(); + logger.debug("Connecting to Netatmo API."); + try { + connectApi.authenticate(credentials, bindingConf.features); + updateStatus(ThingStatus.ONLINE); + getThing().getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler).filter(Objects::nonNull) + .map(CommonInterface.class::cast).forEach(CommonInterface::expireData); + } catch (NetatmoException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + prepareReconnection(); + } + } catch (NetatmoException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + } + } + + private void prepareReconnection() { + connectApi.disconnect(); + freeConnectJob(); + connectJob = Optional + .of(scheduler.schedule(() -> openConnection(), thingConf.reconnectInterval, TimeUnit.SECONDS)); + } + + private void freeConnectJob() { + connectJob.ifPresent(j -> j.cancel(true)); + connectJob = Optional.empty(); + } + + @Override + public void dispose() { + logger.debug("Shutting down Netatmo API bridge handler."); + servlet.ifPresent(servlet -> servlet.dispose()); + servlet = Optional.empty(); + connectApi.dispose(); + freeConnectJob(); + super.dispose(); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("Netatmo Bridge is read-only and does not handle commands"); + } + + @Override + public Collection> getServices() { + return Set.of(NetatmoDiscoveryService.class); + } + + @SuppressWarnings("unchecked") + public @Nullable T getRestManager(Class clazz) { + if (!managers.containsKey(clazz)) { + try { + Constructor constructor = clazz.getConstructor(getClass()); + T instance = constructor.newInstance(this); + Set expected = instance.getRequiredScopes(); + if (connectApi.matchesScopes(expected)) { + managers.put(clazz, instance); + } else { + logger.info("Unable to instantiate {}, expected scope {} is not active", clazz, expected); + } + } catch (SecurityException | ReflectiveOperationException e) { + logger.warn("Error invoking RestManager constructor for class {} : {}", clazz, e.getMessage()); + } + } + return (T) managers.get(clazz); + } + + public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, @Nullable String payload, + @Nullable String contentType, int retryCount) throws NetatmoException { + try { + logger.trace("executeUri {} {} ", method.toString(), uri); + + Request request = httpClient.newRequest(uri).method(method).timeout(TIMEOUT_S, TimeUnit.SECONDS); + + String auth = connectApi.getAuthorization(); + if (auth != null) { + request.header(HttpHeader.AUTHORIZATION, auth); + } + + if (payload != null && contentType != null + && (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method))) { + InputStream stream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8)); + try (InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider(stream)) { + request.content(inputStreamContentProvider, contentType); + } + } + + ContentResponse response = request.send(); + + Code statusCode = HttpStatus.getCode(response.getStatus()); + String responseBody = new String(response.getContent(), StandardCharsets.UTF_8); + logger.trace("executeUri returned : code {} body {}", statusCode, responseBody); + + if (statusCode != Code.OK) { + ApiError error = deserializer.deserialize(ApiError.class, responseBody); + throw new NetatmoException(error); + } + return deserializer.deserialize(clazz, responseBody); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage())); + } catch (TimeoutException | ExecutionException e) { + if (retryCount > 0) { + logger.debug("Request timedout, retry counter : {}", retryCount); + return executeUri(uri, method, clazz, payload, contentType, retryCount - 1); + } + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/request-time-out"); + prepareReconnection(); + throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage())); + } + } + + public BindingConfiguration getConfiguration() { + return bindingConf; + } + + public Optional getServlet() { + return servlet; + } + + public NADeserializer getDeserializer() { + return deserializer; + } + + public boolean isConnected() { + return connectApi.isConnected(); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java new file mode 100644 index 0000000000000..aa519e21bbee6 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java @@ -0,0 +1,220 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.config.NAThingConfiguration; +import org.openhab.binding.netatmo.internal.handler.capability.Capability; +import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap; +import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability; +import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability; +import org.openhab.binding.netatmo.internal.handler.capability.RestCapability; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BridgeHandler; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ChannelKind; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.slf4j.Logger; + +/** + * {@link CommonInterface} defines common methods of AccountHandler and NAThingHandlers used by Capabilities + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public interface CommonInterface { + Thing getThing(); + + ThingBuilder editThing(); + + CapabilityMap getCapabilities(); + + Logger getLogger(); + + ScheduledExecutorService getScheduler(); + + boolean isLinked(ChannelUID channelUID); + + void updateState(ChannelUID channelUID, State state); + + void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail, + @Nullable String thingStatusReason); + + void triggerChannel(String channelID, String event); + + void updateThing(Thing thing); + + @Nullable + Bridge getBridge(); + + default @Nullable CommonInterface getBridgeHandler() { + Bridge bridge = getBridge(); + return bridge != null && bridge.getHandler() instanceof DeviceHandler ? (DeviceHandler) bridge.getHandler() + : null; + } + + default @Nullable ApiBridgeHandler getAccountHandler() { + Bridge bridge = getBridge(); + BridgeHandler bridgeHandler = null; + if (bridge != null) { + bridgeHandler = bridge.getHandler(); + while (bridgeHandler != null && !(bridgeHandler instanceof ApiBridgeHandler)) { + bridge = ((CommonInterface) bridgeHandler).getBridge(); + bridgeHandler = bridge != null ? bridge.getHandler() : null; + } + } + return (ApiBridgeHandler) bridgeHandler; + } + + default @Nullable String getBridgeId() { + CommonInterface bridge = getBridgeHandler(); + return bridge != null ? bridge.getId() : null; + } + + default void expireData() { + getCapabilities().values().forEach(cap -> cap.expireData()); + } + + default String getId() { + return (String) getThing().getConfiguration().get("id"); + } + + default Stream getActiveChannels() { + return getThing().getChannels().stream() + .filter(channel -> ChannelKind.STATE.equals(channel.getKind()) && isLinked(channel.getUID())); + } + + default Optional getHomeHandler() { + CommonInterface bridgeHandler = getBridgeHandler(); + if (bridgeHandler != null) { + return bridgeHandler.getCapabilities().get(HomeCapability.class).isPresent() ? Optional.of(bridgeHandler) + : Optional.empty(); + } + return Optional.empty(); + } + + default List getActiveChildren() { + Thing thing = getThing(); + if (thing instanceof Bridge) { + return ((Bridge) thing).getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler) + .filter(Objects::nonNull).map(CommonInterface.class::cast).collect(Collectors.toList()); + } + return List.of(); + } + + default > Optional getHomeCapability(Class clazz) { + return getHomeHandler().map(handler -> handler.getCapabilities().get(clazz)).orElse(Optional.empty()); + } + + default void setNewData(NAObject newData) { + String finalReason = null; + for (Capability cap : getCapabilities().values()) { + String thingStatusReason = cap.setNewData(newData); + if (thingStatusReason != null) { + finalReason = thingStatusReason; + } + } + if (!newData.isIgnoredForThingUpdate()) { + setThingStatus(finalReason == null ? ThingStatus.ONLINE : ThingStatus.OFFLINE, ThingStatusDetail.NONE, + finalReason); + } + } + + default void commonHandleCommand(ChannelUID channelUID, Command command) { + if (ThingStatus.ONLINE.equals(getThing().getStatus())) { + if (command == RefreshType.REFRESH) { + expireData(); + return; + } + String channelName = channelUID.getIdWithoutGroup(); + getCapabilities().values().forEach(cap -> cap.handleCommand(channelName, command)); + } else { + getLogger().debug("Command {}, on channel {} dropped - thing is not ONLINE", command, channelUID); + } + } + + default void proceedWithUpdate() { + updateReadings().forEach(dataSet -> setNewData(dataSet)); + } + + default List updateReadings() { + List result = new ArrayList<>(); + getCapabilities().values().forEach(cap -> result.addAll(cap.updateReadings())); + getActiveChildren().forEach(child -> result.addAll(child.updateReadings())); + return result; + } + + default void commonInitialize() { + Bridge bridge = getBridge(); + if (bridge == null || bridge.getHandler() == null) { + setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, null); + } else if (!ThingStatus.ONLINE.equals(bridge.getStatus())) { + setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null); + removeRefreshCapability(); + } else { + setThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null); + setRefreshCapability(); + getCapabilities().values().forEach(cap -> cap.initialize()); + getScheduler().schedule(() -> { + CommonInterface bridgeHandler = getBridgeHandler(); + if (bridgeHandler != null) { + bridgeHandler.expireData(); + } + }, 1, TimeUnit.SECONDS); + } + } + + default void setRefreshCapability() { + ModuleType moduleType = ModuleType.from(getThing().getThingTypeUID()); + if (ModuleType.ACCOUNT.equals(moduleType.getBridge())) { + NAThingConfiguration config = getThing().getConfiguration().as(NAThingConfiguration.class); + getCapabilities().put(new RefreshCapability(this, getScheduler(), config.refreshInterval)); + } + } + + default void removeRefreshCapability() { + Capability refreshCap = getCapabilities().remove(RefreshCapability.class); + if (refreshCap != null) { + refreshCap.dispose(); + } + } + + default void commonDispose() { + getCapabilities().values().forEach(Capability::dispose); + } + + default void removeChannels(List channels) { + ThingBuilder builder = editThing().withoutChannels(channels); + updateThing(builder.build()); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/DeviceHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/DeviceHandler.java new file mode 100644 index 0000000000000..6638f52800dae --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/DeviceHandler.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler; + +import java.util.concurrent.ScheduledExecutorService; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.builder.BridgeBuilder; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link DeviceHandler} is the base class for all Netatmo bridges + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class DeviceHandler extends BaseBridgeHandler implements CommonInterface { + private final Logger logger = LoggerFactory.getLogger(DeviceHandler.class); + private CapabilityMap capabilities = new CapabilityMap(); + + public DeviceHandler(Bridge bridge) { + super(bridge); + } + + @Override + public void initialize() { + logger.debug("Initializing handler for bridge {}", getThing().getUID()); + commonInitialize(); + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + logger.debug("bridgeStatusChanged for bridge {} to {}", getThing().getUID(), bridgeStatusInfo); + commonInitialize(); + } + + @Override + public void dispose() { + commonDispose(); + super.dispose(); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + commonHandleCommand(channelUID, command); + } + + @Override + public void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail, + @Nullable String thingStatusReason) { + updateStatus(thingStatus, thingStatusDetail, thingStatusReason); + } + + @Override + public CapabilityMap getCapabilities() { + return capabilities; + } + + @Override + public BridgeBuilder editThing() { + return super.editThing(); + } + + @Override + public void updateThing(Thing thing) { + super.updateThing(thing); + } + + @Override + public void updateState(ChannelUID channelUID, State state) { + super.updateState(channelUID, state); + } + + @Override + public boolean isLinked(ChannelUID channelUID) { + return super.isLinked(channelUID); + } + + @Override + public @Nullable Bridge getBridge() { + return super.getBridge(); + } + + @Override + public void triggerChannel(String channelID, String event) { + super.triggerChannel(channelID, event); + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public ScheduledExecutorService getScheduler() { + return scheduler; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ModuleHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ModuleHandler.java new file mode 100644 index 0000000000000..5e5d5db0c11f5 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ModuleHandler.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link ModuleHandler} is the base class for all Netatmo things + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class ModuleHandler extends BaseThingHandler implements CommonInterface { + private final Logger logger = LoggerFactory.getLogger(ModuleHandler.class); + private CapabilityMap capabilities = new CapabilityMap(); + + public ModuleHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + logger.debug("Initializing handler for thing {}", getThing().getUID()); + commonInitialize(); + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + logger.debug("bridgeStatusChanged for thing {} to {}", getThing().getUID(), bridgeStatusInfo); + commonInitialize(); + } + + @Override + public void dispose() { + commonDispose(); + super.dispose(); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + commonHandleCommand(channelUID, command); + } + + @Override + public void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail, + @Nullable String thingStatusReason) { + updateStatus(thingStatus, thingStatusDetail, thingStatusReason); + } + + @Override + public CapabilityMap getCapabilities() { + return capabilities; + } + + @Override + public ThingBuilder editThing() { + return super.editThing(); + } + + @Override + public void updateThing(Thing thing) { + super.updateThing(thing); + } + + @Override + public void updateState(ChannelUID channelUID, State state) { + super.updateState(channelUID, state); + } + + @Override + public boolean isLinked(ChannelUID channelUID) { + return super.isLinked(channelUID); + } + + @Override + public @Nullable Bridge getBridge() { + return super.getBridge(); + } + + @Override + public void triggerChannel(String channelID, String event) { + super.triggerChannel(channelID, event); + } + + @Override + public Collection> getServices() { + List> result = new ArrayList<>(); + capabilities.values().forEach(cap -> result.addAll(cap.getServices())); + return result; + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public ScheduledExecutorService getScheduler() { + return scheduler; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoBridgeHandler.java deleted file mode 100644 index de6d8febfceb9..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.handler; - -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; - -import org.apache.oltu.oauth2.client.OAuthClient; -import org.apache.oltu.oauth2.client.URLConnectionClient; -import org.apache.oltu.oauth2.client.request.OAuthClientRequest; -import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; -import org.apache.oltu.oauth2.common.message.types.GrantType; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.config.NetatmoBridgeConfiguration; -import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent; -import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEventPerson; -import org.openhab.binding.netatmo.internal.webhook.WelcomeWebHookServlet; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.thing.binding.BaseBridgeHandler; -import org.openhab.core.types.Command; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.swagger.client.ApiClient; -import io.swagger.client.ApiException; -import io.swagger.client.api.HealthyhomecoachApi; -import io.swagger.client.api.PartnerApi; -import io.swagger.client.api.StationApi; -import io.swagger.client.api.ThermostatApi; -import io.swagger.client.api.WelcomeApi; -import io.swagger.client.auth.Authentication; -import io.swagger.client.auth.OAuth; -import io.swagger.client.model.NAHealthyHomeCoachDataBody; -import io.swagger.client.model.NAMeasureBodyElem; -import io.swagger.client.model.NAStationDataBody; -import io.swagger.client.model.NAThermostatDataBody; -import io.swagger.client.model.NAWelcomeHomeData; - -/** - * {@link NetatmoBridgeHandler} is the handler for a Netatmo API and connects it - * to the framework. The devices and modules uses the - * {@link NetatmoBridgeHandler} to request informations about their status - * - * @author Gaël L'hopital - Initial contribution OH2 version - * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules - * - */ -@NonNullByDefault -public class NetatmoBridgeHandler extends BaseBridgeHandler { - private final Logger logger = LoggerFactory.getLogger(NetatmoBridgeHandler.class); - - public NetatmoBridgeConfiguration configuration = new NetatmoBridgeConfiguration(); - private @Nullable ScheduledFuture refreshJob; - private @Nullable APICreator apiCreator; - private @Nullable WelcomeWebHookServlet webHookServlet; - private List 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 bf42b2f959bce..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.handler; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link NetatmoDataListener} allows receiving notification when any netatmo device thing handler - * is getting refreshed data from the netatmo server. - * - * @author Laurent Garnier - Initial contribution - */ -@NonNullByDefault -public interface NetatmoDataListener { - - /** - * This method is called just after the thing handler fetched new data from the netatmo server. - * - * @param data the retrieved data. - */ - public void onDataRefreshed(Object data); -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDeviceHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDeviceHandler.java deleted file mode 100644 index dbd9f7a38fc37..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoDeviceHandler.java +++ /dev/null @@ -1,257 +0,0 @@ -/** - * Copyright (c) 2010-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.handler; - -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.ChannelTypeUtils; -import org.openhab.binding.netatmo.internal.RefreshStrategy; -import org.openhab.core.config.core.Configuration; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.PointType; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.swagger.client.ApiException; -import io.swagger.client.model.NAPlace; - -/** - * {@link NetatmoDeviceHandler} is the handler for a given - * device accessed through the Netatmo Bridge - * - * @author Gaël L'hopital - Initial contribution - */ -@NonNullByDefault -public abstract class NetatmoDeviceHandler extends AbstractNetatmoThingHandler { - - private static final int MIN_REFRESH_INTERVAL = 2000; - private static final int DEFAULT_REFRESH_INTERVAL = 300000; - - private final Logger logger = LoggerFactory.getLogger(NetatmoDeviceHandler.class); - private final Object updateLock = new Object(); - private @Nullable ScheduledFuture refreshJob; - private @Nullable RefreshStrategy refreshStrategy; - private @Nullable DEVICE device; - protected Map childs = new ConcurrentHashMap<>(); - - public NetatmoDeviceHandler(Thing thing, final TimeZoneProvider timeZoneProvider) { - super(thing, timeZoneProvider); - } - - @Override - protected void initializeThing() { - defineRefreshInterval(); - updateStatus(ThingStatus.ONLINE); - scheduleRefreshJob(); - } - - private void scheduleRefreshJob() { - RefreshStrategy strategy = refreshStrategy; - if (strategy == null) { - return; - } - long delay = strategy.nextRunDelayInS(); - logger.debug("Scheduling update channel thread in {} s", delay); - refreshJob = scheduler.schedule(() -> { - updateChannels(false); - ScheduledFuture job = refreshJob; - if (job != null) { - job.cancel(false); - refreshJob = null; - } - scheduleRefreshJob(); - }, delay, TimeUnit.SECONDS); - } - - @Override - public void dispose() { - logger.debug("Running dispose()"); - ScheduledFuture job = refreshJob; - if (job != null) { - job.cancel(true); - refreshJob = null; - } - } - - protected abstract Optional updateReadings(); - - protected void updateProperties(DEVICE deviceData) { - } - - @Override - protected void updateChannels() { - updateChannels(true); - } - - private void updateChannels(boolean requireDefinedRefreshInterval) { - // Avoid concurrent data readings - synchronized (updateLock) { - RefreshStrategy strategy = refreshStrategy; - if (strategy != null) { - logger.debug("Data aged of {} s", strategy.dataAge() / 1000); - boolean dataOutdated = (requireDefinedRefreshInterval && strategy.isSearchingRefreshInterval()) ? false - : strategy.isDataOutdated(); - if (dataOutdated) { - logger.debug("Trying to update channels on device {}", getId()); - childs.clear(); - - Optional newDeviceReading = Optional.empty(); - try { - newDeviceReading = updateReadings(); - } catch (ApiException e) { - if (logger.isDebugEnabled()) { - // we also attach the stack trace - logger.error("Unable to connect Netatmo API : {}", e.getMessage(), e); - } else { - logger.error("Unable to connect Netatmo API : {}", e.getMessage()); - } - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Unable to connect Netatmo API : " + e.getLocalizedMessage()); - } - if (newDeviceReading.isPresent()) { - logger.debug("Successfully updated device {} readings! Now updating channels", getId()); - DEVICE theDevice = newDeviceReading.get(); - this.device = theDevice; - updateStatus(isReachable() ? ThingStatus.ONLINE : ThingStatus.OFFLINE); - updateProperties(theDevice); - getDataTimestamp().ifPresent(dataTimeStamp -> { - strategy.setDataTimeStamp(dataTimeStamp, timeZoneProvider.getTimeZone()); - }); - getRadioHelper().ifPresent(helper -> helper.setModule(theDevice)); - getBridgeHandler().ifPresent(handler -> { - handler.checkForNewThings(theDevice); - }); - } else { - logger.debug("Failed to update device {} readings! Skip updating channels", getId()); - } - // Be sure that all channels for the modules will be updated with refreshed data - childs.forEach((childId, moduleData) -> { - findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> { - naChildModule.setRefreshRequired(true); - }); - }); - } else { - logger.debug("Data still valid for device {}", getId()); - } - super.updateChannels(); - updateChildModules(); - } - } - } - - @Override - protected State getNAThingProperty(String channelId) { - try { - Optional dev = getDevice(); - switch (channelId) { - case CHANNEL_LAST_STATUS_STORE: - if (dev.isPresent()) { - Method getLastStatusStore = dev.get().getClass().getMethod("getLastStatusStore"); - Integer lastStatusStore = (Integer) getLastStatusStore.invoke(dev.get()); - return ChannelTypeUtils.toDateTimeType(lastStatusStore, timeZoneProvider.getTimeZone()); - } else { - return UnDefType.UNDEF; - } - case CHANNEL_LOCATION: - if (dev.isPresent()) { - Method getPlace = dev.get().getClass().getMethod("getPlace"); - NAPlace place = (NAPlace) getPlace.invoke(dev.get()); - PointType point = new PointType(new DecimalType(place.getLocation().get(1)), - new DecimalType(place.getLocation().get(0))); - if (place.getAltitude() != null) { - point.setAltitude(new DecimalType(place.getAltitude())); - } - return point; - } else { - return UnDefType.UNDEF; - } - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - logger.debug("The device has no method to access {} property ", channelId); - return UnDefType.NULL; - } - - return super.getNAThingProperty(channelId); - } - - private void updateChildModules() { - logger.debug("Updating child modules of {}", getId()); - childs.forEach((childId, moduleData) -> { - findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> { - logger.debug("Updating child module {}", naChildModule.getId()); - naChildModule.updateChannels(moduleData); - }); - }); - } - - /* - * Sets the refresh rate of the device depending whether it's a property - * of the thing or if it's defined by configuration - */ - private void defineRefreshInterval() { - BigDecimal dataValidityPeriod; - if (thing.getProperties().containsKey(PROPERTY_REFRESH_PERIOD)) { - String refreshPeriodProperty = thing.getProperties().get(PROPERTY_REFRESH_PERIOD); - if ("auto".equalsIgnoreCase(refreshPeriodProperty)) { - dataValidityPeriod = new BigDecimal(-1); - } else { - dataValidityPeriod = new BigDecimal(refreshPeriodProperty); - } - } else { - Configuration conf = config; - Object interval = conf != null ? conf.get(REFRESH_INTERVAL) : null; - if (interval instanceof BigDecimal) { - dataValidityPeriod = (BigDecimal) interval; - if (dataValidityPeriod.intValue() < MIN_REFRESH_INTERVAL) { - logger.info( - "Refresh interval setting is too small for thing {}, {} ms is considered as refresh interval.", - thing.getUID(), MIN_REFRESH_INTERVAL); - dataValidityPeriod = new BigDecimal(MIN_REFRESH_INTERVAL); - } - } else { - dataValidityPeriod = new BigDecimal(DEFAULT_REFRESH_INTERVAL); - } - } - refreshStrategy = new RefreshStrategy(dataValidityPeriod.intValue()); - } - - protected abstract Optional getDataTimestamp(); - - public void expireData() { - RefreshStrategy strategy = refreshStrategy; - if (strategy != null) { - strategy.expireData(); - } - } - - protected Optional getDevice() { - return Optional.ofNullable(device); - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoModuleHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/NetatmoModuleHandler.java deleted file mode 100644 index 9c9f7b1982ec7..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.handler; - -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.ChannelTypeUtils; -import org.openhab.core.config.core.Configuration; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * {@link NetatmoModuleHandler} is the handler for a given - * module device accessed through the Netatmo Device - * - * @author Gaël L'hopital - Initial contribution - */ -@NonNullByDefault -public class NetatmoModuleHandler 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/capability/AirCareCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AirCareCapability.java new file mode 100644 index 0000000000000..9e55e7c26bb2b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AirCareCapability.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.AircareApi; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link AirCareCapability} give the ability to read home coach api + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class AirCareCapability extends RestCapability { + private final Logger logger = LoggerFactory.getLogger(AirCareCapability.class); + + public AirCareCapability(CommonInterface handler) { + super(handler, AircareApi.class); + } + + @Override + protected List updateReadings(AircareApi api) { + try { + return List.of(api.getHomeCoach(handler.getId())); + } catch (NetatmoException e) { + logger.warn("Error retrieving home-coach data '{}' : {}", handler.getId(), e.getMessage()); + } + return List.of(); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java new file mode 100644 index 0000000000000..8f6246dbc18be --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.AlimentationStatus; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SdCardStatus; +import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson; +import org.openhab.binding.netatmo.internal.api.dto.HomeEvent; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; +import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; + +/** + * {@link CameraCapability} give to handle Welcome Camera specifics + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class CameraCapability extends HomeSecurityThingCapability { + private final CameraChannelHelper cameraHelper; + private final ChannelUID personChannelUID; + + protected @Nullable String localUrl; + + public CameraCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider, + List channelHelpers) { + super(handler, descriptionProvider, channelHelpers); + this.personChannelUID = new ChannelUID(thing.getUID(), GROUP_LAST_EVENT, CHANNEL_EVENT_PERSON_ID); + this.cameraHelper = (CameraChannelHelper) channelHelpers.stream().filter(c -> c instanceof CameraChannelHelper) + .findFirst().orElseThrow(() -> new IllegalArgumentException( + "CameraCapability must find a CameraChannelHelper, please file a bug report.")); + } + + @Override + public void updateHomeStatusModule(HomeStatusModule newData) { + super.updateHomeStatusModule(newData); + String vpnUrl = newData.getVpnUrl(); + if (vpnUrl != null) { + localUrl = newData.isLocal() ? securityCapability.map(cap -> cap.ping(vpnUrl)).orElse(null) : null; + cameraHelper.setUrls(vpnUrl, localUrl); + eventHelper.setUrls(vpnUrl, localUrl); + } + if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus()) + || !AlimentationStatus.ALIM_CORRECT_POWER.equals(newData.getAlimStatus())) { + statusReason = String.format("%s, %s", newData.getSdStatus(), newData.getAlimStatus()); + } + } + + @Override + public void handleCommand(String channelName, Command command) { + if (command instanceof OnOffType && CHANNEL_MONITORING.equals(channelName)) { + securityCapability.ifPresent(cap -> cap.changeStatus(localUrl, OnOffType.ON.equals(command))); + } else { + super.handleCommand(channelName, command); + } + } + + @Override + protected void beforeNewData() { + super.beforeNewData(); + homeCapability.ifPresent(cap -> { + NAObjectMap persons = cap.getPersons(); + descriptionProvider.setStateOptions(personChannelUID, persons.values().stream() + .map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList())); + }); + } + + @Override + public List updateReadings() { + List result = new ArrayList<>(); + securityCapability.ifPresent(cap -> { + Collection events = cap.getCameraEvents(handler.getId()); + if (!events.isEmpty()) { + result.add(events.iterator().next()); + } + }); + return result; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/Capability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/Capability.java new file mode 100644 index 0000000000000..a2ef48a640c77 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/Capability.java @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.VENDOR; +import static org.openhab.core.thing.Thing.*; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.binding.netatmo.internal.api.dto.Device; +import org.openhab.binding.netatmo.internal.api.dto.Event; +import org.openhab.binding.netatmo.internal.api.dto.HomeData; +import org.openhab.binding.netatmo.internal.api.dto.HomeEvent; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus; +import org.openhab.binding.netatmo.internal.api.dto.NAMain; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; + +/** + * The {@link Capability} is the base class for all inherited capabilities + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class Capability { + + protected final Thing thing; + protected final CommonInterface handler; + protected final ModuleType moduleType; + + protected boolean firstLaunch; + protected Map properties = Map.of(); + protected @Nullable String statusReason; + + Capability(CommonInterface handler) { + this.handler = handler; + this.thing = handler.getThing(); + this.moduleType = ModuleType.from(thing.getThingTypeUID()); + } + + public final @Nullable String setNewData(NAObject newData) { + beforeNewData(); + if (newData instanceof HomeData) { + updateHomeData((HomeData) newData); + } + if (newData instanceof HomeStatus) { + updateHomeStatus((HomeStatus) newData); + } + if (newData instanceof HomeStatusModule) { + updateHomeStatusModule((HomeStatusModule) newData); + } + if (newData instanceof Event) { + updateEvent((Event) newData); + } + if (newData instanceof HomeEvent) { + updateHomeEvent((HomeEvent) newData); + } + if (newData instanceof NAThing) { + updateNAThing((NAThing) newData); + } + if (newData instanceof NAMain) { + updateNAMain((NAMain) newData); + } + if (newData instanceof Device) { + updateNADevice((Device) newData); + } + afterNewData(newData); + return statusReason; + } + + protected void beforeNewData() { + properties = new HashMap<>(thing.getProperties()); + firstLaunch = properties.isEmpty(); + if (firstLaunch && !moduleType.isLogical()) { + properties.put(PROPERTY_VENDOR, VENDOR); + properties.put(PROPERTY_MODEL_ID, moduleType.name()); + } + statusReason = null; + } + + protected void afterNewData(@Nullable NAObject newData) { + if (!properties.equals(thing.getProperties())) { + thing.setProperties(properties); + } + } + + protected void updateNAThing(NAThing newData) { + String firmware = newData.getFirmware(); + if (firmware != null && !firmware.isBlank()) { + properties.put(PROPERTY_FIRMWARE_VERSION, firmware); + } + if (!newData.isReachable()) { + statusReason = "@text/device-not-connected"; + } + } + + protected void updateNAMain(NAMain newData) { + // do nothing by default, can be overridden by subclasses + } + + protected void updateHomeEvent(HomeEvent newData) { + // do nothing by default, can be overridden by subclasses + } + + protected void updateHomeStatus(HomeStatus newData) { + // do nothing by default, can be overridden by subclasses + } + + protected void updateHomeData(HomeData newData) { + // do nothing by default, can be overridden by subclasses + } + + protected void updateEvent(Event newData) { + // do nothing by default, can be overridden by subclasses + } + + protected void updateNADevice(Device newData) { + // do nothing by default, can be overridden by subclasses + } + + public void initialize() { + // do nothing by default, can be overridden by subclasses + } + + public void expireData() { + if (!handler.getCapabilities().containsKey(RefreshCapability.class)) { + CommonInterface bridgeHandler = handler.getBridgeHandler(); + if (bridgeHandler != null) { + bridgeHandler.expireData(); + } + } + } + + public void dispose() { + // do nothing by default, can be overridden by subclasses + } + + public void updateHomeStatusModule(HomeStatusModule newData) { + // do nothing by default, can be overridden by subclasses + } + + public void handleCommand(String channelName, Command command) { + // do nothing by default, can be overridden by subclasses + } + + public Collection> getServices() { + return List.of(); + } + + public List updateReadings() { + return List.of(); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CapabilityMap.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CapabilityMap.java new file mode 100644 index 0000000000000..3cbe3f1124c7b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CapabilityMap.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * {@link CapabilityMap} is a specialized Map designed to store capabilities + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class CapabilityMap extends ConcurrentHashMap, Capability> { + private static final long serialVersionUID = -3043492242108419801L; + + public void put(Capability capability) { + Class clazz = capability.getClass(); + if (super.get(clazz) == null) { + super.put(clazz, capability); + } + } + + public Optional get(Class clazz) { + @SuppressWarnings("unchecked") + T cap = (T) super.get(clazz); + return Optional.ofNullable(cap); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/ChannelHelperCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/ChannelHelperCapability.java new file mode 100644 index 0000000000000..3e2fcf59bf3ae --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/ChannelHelperCapability.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.State; + +/** + * {@link ChannelHelperCapability} give the capability to dispatch incoming data across the channel helpers. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class ChannelHelperCapability extends Capability { + private final List channelHelpers; + + public ChannelHelperCapability(CommonInterface handler, List channelHelpers) { + super(handler); + this.channelHelpers = channelHelpers; + } + + @Override + public void afterNewData(@Nullable NAObject newData) { + super.afterNewData(newData); + channelHelpers.forEach(helper -> helper.setNewData(newData)); + handler.getActiveChannels().forEach(channel -> { + ChannelUID channelUID = channel.getUID(); + String channelID = channelUID.getIdWithoutGroup(); + String groupId = channelUID.getGroupId(); + Configuration channelConfig = channel.getConfiguration(); + for (ChannelHelper helper : channelHelpers) { + State state = helper.getChannelState(channelID, groupId, channelConfig); + if (state != null) { + handler.updateState(channelUID, state); + break; + } + } + }); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/DeviceCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/DeviceCapability.java new file mode 100644 index 0000000000000..2b7db3da593c0 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/DeviceCapability.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.dto.NAMain; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; + +/** + * The {@link DeviceCapability} takes care of handling properties for netatmo devices + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class DeviceCapability extends Capability { + private static final int DATA_AGE_LIMIT_S = 3600; + + public DeviceCapability(CommonInterface handler) { + super(handler); + } + + @Override + protected void updateNAMain(NAMain newData) { + if (firstLaunch) { + newData.getPlace().ifPresent(place -> { + place.getCity().map(city -> properties.put(PROPERTY_CITY, city)); + place.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country)); + place.getTimezone().map(tz -> properties.put(PROPERTY_TIMEZONE, tz)); + }); + } + if (!newData.hasFreshData(DATA_AGE_LIMIT_S)) { + statusReason = "@text/data-over-limit"; + } + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java new file mode 100644 index 0000000000000..328eb26227dd7 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java @@ -0,0 +1,155 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import java.time.ZonedDateTime; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.EnergyApi; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.api.dto.HomeData; +import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule; +import org.openhab.binding.netatmo.internal.api.dto.HomeDataRoom; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus; +import org.openhab.binding.netatmo.internal.api.dto.Room; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link EnergyCapability} is the base class for handler able to handle energy features + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class EnergyCapability extends RestCapability { + private final Logger logger = LoggerFactory.getLogger(EnergyCapability.class); + + private int setPointDefaultDuration = -1; + private final NetatmoDescriptionProvider descriptionProvider; + + EnergyCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) { + super(handler, EnergyApi.class); + this.descriptionProvider = descriptionProvider; + } + + @Override + protected void updateHomeData(HomeData homeData) { + NAObjectMap rooms = homeData.getRooms(); + NAObjectMap modules = homeData.getModules(); + handler.getActiveChildren().forEach(handler -> { + HomeDataRoom roomData = rooms.get(handler.getId()); + if (roomData != null) { + roomData.setIgnoredForThingUpdate(true); + handler.setNewData(roomData); + } + HomeDataModule moduleData = modules.get(handler.getId()); + if (moduleData != null) { + moduleData.setIgnoredForThingUpdate(true); + handler.setNewData(moduleData); + } + }); + descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), GROUP_ENERGY, CHANNEL_PLANNING), + homeData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName())) + .collect(Collectors.toList())); + setPointDefaultDuration = homeData.getThermSetpointDefaultDuration(); + } + + @Override + protected void updateHomeStatus(HomeStatus homeStatus) { + NAObjectMap rooms = homeStatus.getRooms(); + NAObjectMap modules = homeStatus.getModules(); + handler.getActiveChildren().forEach(handler -> { + Room roomData = rooms.get(handler.getId()); + if (roomData != null) { + handler.setNewData(roomData); + } + HomeStatusModule data = modules.get(handler.getId()); + if (data != null) { + handler.setNewData(data); + } + }); + } + + public int getSetpointDefaultDuration() { + return setPointDefaultDuration; + } + + public void setRoomThermMode(String roomId, SetpointMode targetMode) { + getApi().ifPresent(api -> { + try { + api.setThermpoint(handler.getId(), roomId, targetMode, + targetMode == SetpointMode.MAX ? setpointEndTimeFromNow(setPointDefaultDuration) : 0, 0); + handler.expireData(); + } catch (NetatmoException e) { + logger.warn("Error setting room thermostat mode '{}' : {}", targetMode, e.getMessage()); + } + }); + } + + public void setRoomThermTemp(String roomId, double temperature, long endtime, SetpointMode mode) { + getApi().ifPresent(api -> { + try { + api.setThermpoint(handler.getId(), roomId, mode, endtime, temperature); + handler.expireData(); + } catch (NetatmoException e) { + logger.warn("Error setting room thermostat mode '{}' : {}", mode, e.getMessage()); + } + }); + } + + public void setRoomThermTemp(String roomId, double temperature) { + setRoomThermTemp(roomId, temperature, setpointEndTimeFromNow(setPointDefaultDuration), SetpointMode.MANUAL); + } + + @Override + public void handleCommand(String channelName, Command command) { + getApi().ifPresent(api -> { + try { + switch (channelName) { + case CHANNEL_PLANNING: + api.switchSchedule(handler.getId(), command.toString()); + break; + case CHANNEL_SETPOINT_MODE: + SetpointMode targetMode = SetpointMode.valueOf(command.toString()); + if (targetMode == SetpointMode.MANUAL) { + logger.info("Switch to 'Manual' is done by setting a setpoint temp, command ignored"); + return; + } + api.setThermMode(handler.getId(), targetMode.apiDescriptor); + break; + } + handler.expireData(); + } catch (NetatmoException e) { + logger.warn("Error handling command '{}' : {}", command, e.getMessage()); + } catch (IllegalArgumentException e) { + logger.warn("Command '{}' sent to channel '{}' is not a valid setpoint mode.", command, channelName); + } + }); + } + + private static long setpointEndTimeFromNow(int duration_min) { + return ZonedDateTime.now().plusMinutes(duration_min).toEpochSecond(); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EventCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EventCapability.java new file mode 100644 index 0000000000000..a72abc4066508 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EventCapability.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet; + +/** + * {@link EventCapability} is the base class for handlers + * subject to receive event notifications. This class registers to webhookservlet so + * it can be notified when an event arrives. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class EventCapability extends Capability { + private Optional servlet = Optional.empty(); + + public EventCapability(CommonInterface handler) { + super(handler); + } + + @Override + public void initialize() { + ApiBridgeHandler accountHandler = handler.getAccountHandler(); + if (accountHandler != null) { + servlet = accountHandler.getServlet(); + servlet.ifPresent(s -> s.registerDataListener(handler.getId(), this)); + } + } + + @Override + public void dispose() { + servlet.ifPresent(s -> s.unregisterDataListener(this)); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java new file mode 100644 index 0000000000000..0e001bafd435a --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeCapability.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.HomeApi; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; +import org.openhab.binding.netatmo.internal.api.dto.HomeData; +import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule; +import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson; +import org.openhab.binding.netatmo.internal.api.dto.Location; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link HomeCapability} is the base class for handler able to manage persons and modules + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HomeCapability extends RestCapability { + private final Logger logger = LoggerFactory.getLogger(HomeCapability.class); + + private final NetatmoDescriptionProvider descriptionProvider; + + private NAObjectMap persons = new NAObjectMap<>(); + private NAObjectMap modules = new NAObjectMap<>(); + + private Set featuresArea = Set.of(); + + public HomeCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) { + super(handler, HomeApi.class); + this.descriptionProvider = descriptionProvider; + } + + @Override + protected void updateHomeData(HomeData home) { + featuresArea = home.getFeatures(); + if (hasFeature(FeatureArea.SECURITY) && !handler.getCapabilities().containsKey(SecurityCapability.class)) { + handler.getCapabilities().put(new SecurityCapability(handler)); + } + if (hasFeature(FeatureArea.ENERGY) && !handler.getCapabilities().containsKey(EnergyCapability.class)) { + handler.getCapabilities().put(new EnergyCapability(handler, descriptionProvider)); + } + if (firstLaunch) { + home.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country)); + home.getTimezone().map(tz -> properties.put(PROPERTY_TIMEZONE, tz)); + properties.put(GROUP_LOCATION, ((Location) home).getLocation().toString()); + properties.put(PROPERTY_FEATURE, featuresArea.stream().map(f -> f.name()).collect(Collectors.joining(","))); + } + } + + @Override + protected void afterNewData(@Nullable NAObject newData) { + super.afterNewData(newData); + if (firstLaunch && !hasFeature(FeatureArea.SECURITY)) { + handler.removeChannels(thing.getChannelsOfGroup(GROUP_SECURITY)); + } + if (firstLaunch && !hasFeature(FeatureArea.ENERGY)) { + handler.removeChannels(thing.getChannelsOfGroup(GROUP_ENERGY)); + } + } + + private boolean hasFeature(FeatureArea seeked) { + return featuresArea.contains(seeked); + } + + public NAObjectMap getPersons() { + return persons; + } + + public NAObjectMap getModules() { + return modules; + } + + @Override + protected List updateReadings(HomeApi api) { + List result = new ArrayList<>(); + try { + HomeData homeData = api.getHomeData(handler.getId()); + if (homeData != null) { + result.add(homeData); + persons = homeData.getPersons(); + modules = homeData.getModules(); + } + HomeStatus homeStatus = api.getHomeStatus(handler.getId()); + if (homeStatus != null) { + result.add(homeStatus); + } + } catch (NetatmoException e) { + logger.warn("Error gettting Home informations : {}", e.getMessage()); + } + return result; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeSecurityThingCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeSecurityThingCapability.java new file mode 100644 index 0000000000000..0b808fab93bfb --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/HomeSecurityThingCapability.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import java.util.List; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.EventChannelHelper; +import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; + +/** + * {@link HomeSecurityThingCapability} is the ancestor of capabilities hosted by a security home + * e.g. person and camera capabilities + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HomeSecurityThingCapability extends Capability { + protected final NetatmoDescriptionProvider descriptionProvider; + protected final EventChannelHelper eventHelper; + + protected Optional securityCapability = Optional.empty(); + protected Optional homeCapability = Optional.empty(); + + public HomeSecurityThingCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider, + List channelHelpers) { + super(handler); + this.descriptionProvider = descriptionProvider; + this.eventHelper = (EventChannelHelper) channelHelpers.stream().filter(c -> c instanceof EventChannelHelper) + .findFirst().orElseThrow(() -> new IllegalArgumentException( + "HomeSecurityThingCapability must find an EventChannelHelper, please file a bug report.")); + eventHelper.setModuleType(moduleType); + } + + @Override + public void initialize() { + super.initialize(); + securityCapability = handler.getHomeCapability(SecurityCapability.class); + homeCapability = handler.getHomeCapability(HomeCapability.class); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/MeasureCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/MeasureCapability.java new file mode 100644 index 0000000000000..39b312372e906 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/MeasureCapability.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.WeatherApi; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.config.MeasureConfiguration; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; +import org.openhab.binding.netatmo.internal.handler.channelhelper.MeasuresChannelHelper; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link MeasureCapability} is the base class for handler able to handle user defined measures + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class MeasureCapability extends RestCapability { + private final Logger logger = LoggerFactory.getLogger(MeasureCapability.class); + private final Map measures = new HashMap<>(); + + public MeasureCapability(CommonInterface handler, List helpers) { + super(handler, WeatherApi.class); + MeasuresChannelHelper measureChannelHelper = (MeasuresChannelHelper) helpers.stream() + .filter(c -> c instanceof MeasuresChannelHelper).findFirst() + .orElseThrow(() -> new IllegalArgumentException( + "MeasureCapability must find a MeasuresChannelHelper, please file a bug report.")); + measureChannelHelper.setMeasures(measures); + } + + @Override + public List updateReadings(WeatherApi api) { + String bridgeId = handler.getBridgeId(); + String deviceId = bridgeId != null ? bridgeId : handler.getId(); + String moduleId = bridgeId != null ? handler.getId() : null; + updateMeasures(api, deviceId, moduleId); + return List.of(); + } + + private void updateMeasures(WeatherApi api, String deviceId, @Nullable String moduleId) { + measures.clear(); + handler.getActiveChannels().filter(channel -> !channel.getConfiguration().getProperties().isEmpty()) + .forEach(channel -> { + ChannelTypeUID channelTypeUID = channel.getChannelTypeUID(); + if (channelTypeUID != null) { + MeasureConfiguration measureDef = channel.getConfiguration().as(MeasureConfiguration.class); + String descriptor = channelTypeUID.getId().split("-")[0]; + try { + Object result = measureDef.limit.isBlank() + ? api.getMeasures(deviceId, moduleId, measureDef.period, descriptor) + : api.getMeasures(deviceId, moduleId, measureDef.period, descriptor, + measureDef.limit); + MeasureClass.AS_SET.stream().filter(mc -> mc.apiDescriptor.equals(descriptor)).findFirst() + .ifPresent(mc -> { + State state = result instanceof ZonedDateTime + ? toDateTimeType((ZonedDateTime) result) + : result instanceof Double ? toQuantityType((Double) result, mc) + : UnDefType.UNDEF; + measures.put(channel.getUID().getIdWithoutGroup(), state); + }); + } catch (NetatmoException e) { + logger.warn("Error getting measures for channel {}, check configuration", + channel.getLabel()); + } + } + }); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PersonCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PersonCapability.java new file mode 100644 index 0000000000000..3ef4d112f67fa --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PersonCapability.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.EventType; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.binding.netatmo.internal.api.dto.Event; +import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule; +import org.openhab.binding.netatmo.internal.api.dto.HomeEvent; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; +import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; + +/** + * {@link PersonCapability} gives the ability to handle Person specifics + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PersonCapability extends HomeSecurityThingCapability { + private final ChannelUID cameraChannelUID; + private @Nullable ZonedDateTime lastEventTime; + + public PersonCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider, + List channelHelpers) { + super(handler, descriptionProvider, channelHelpers); + this.cameraChannelUID = new ChannelUID(thing.getUID(), GROUP_PERSON_EVENT, CHANNEL_EVENT_CAMERA_ID); + } + + @Override + protected void beforeNewData() { + super.beforeNewData(); + homeCapability.ifPresent(cap -> { + Stream cameras = cap.getModules().values().stream() + .filter(module -> module.getType() == ModuleType.WELCOME); + descriptionProvider.setStateOptions(cameraChannelUID, + cameras.map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList())); + }); + } + + @Override + public void handleCommand(String channelName, Command command) { + if ((command instanceof OnOffType) && CHANNEL_PERSON_AT_HOME.equals(channelName)) { + securityCapability.ifPresent(cap -> cap.setPersonAway(handler.getId(), OnOffType.OFF.equals(command))); + } + } + + @Override + public void updateEvent(Event event) { + super.updateEvent(event); + EventType eventType = event.getEventType(); + ZonedDateTime localLast = lastEventTime; + ZonedDateTime eventTime = event.getTime(); + if ((localLast != null && !eventTime.isAfter(localLast)) || !eventType.appliesOn(ModuleType.PERSON)) { + return; // ignore incoming events if they are deprecated + } + lastEventTime = eventTime; + handler.triggerChannel(CHANNEL_HOME_EVENT, + event.getSubTypeDescription().map(st -> st.name()).orElse(event.getEventType().name())); + } + + @Override + public List updateReadings() { + List result = new ArrayList<>(); + securityCapability.ifPresent(cap -> { + Collection events = cap.getPersonEvents(handler.getId()); + if (!events.isEmpty()) { + result.add(events.iterator().next()); + } + }); + return result; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PresenceCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PresenceCapability.java new file mode 100644 index 0000000000000..d800dccf59f44 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PresenceCapability.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.CHANNEL_FLOODLIGHT; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; +import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link PresenceCapability} give to handle Presence Camera specifics + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PresenceCapability extends CameraCapability { + private final Logger logger = LoggerFactory.getLogger(PresenceCapability.class); + + public PresenceCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider, + List channelHelpers) { + super(handler, descriptionProvider, channelHelpers); + } + + @Override + public void handleCommand(String channelName, Command command) { + if (CHANNEL_FLOODLIGHT.equals(channelName)) { + if (command instanceof OnOffType) { + changeFloodlightMode(command == OnOffType.ON ? FloodLightMode.ON : FloodLightMode.OFF); + return; + } else if (command instanceof StringType) { + try { + FloodLightMode mode = FloodLightMode.valueOf(command.toString()); + changeFloodlightMode(mode); + } catch (IllegalArgumentException e) { + logger.info("Incorrect command '{}' received for channel '{}'", command, channelName); + } + return; + } + } + super.handleCommand(channelName, command); + } + + private void changeFloodlightMode(FloodLightMode mode) { + securityCapability.ifPresent(cap -> cap.changeFloodlightMode(localUrl, mode)); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RefreshCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RefreshCapability.java new file mode 100644 index 0000000000000..87c8d4317937f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RefreshCapability.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import static java.time.temporal.ChronoUnit.*; + +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.core.thing.ThingStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link RefreshCapability} is the class used to embed the refreshing needs calculation for devices + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class RefreshCapability extends Capability { + private static final Duration DEFAULT_DELAY = Duration.of(20, SECONDS); + private static final Duration PROBING_INTERVAL = Duration.of(120, SECONDS); + private static final Duration OFFLINE_INTERVAL = Duration.of(15, MINUTES); + + private final Logger logger = LoggerFactory.getLogger(RefreshCapability.class); + private final ScheduledExecutorService scheduler; + + private Duration dataValidity; + private Instant dataTimeStamp = Instant.now(); + private Instant dataTimeStamp0 = Instant.MIN; + private Optional> refreshJob = Optional.empty(); + private final boolean refreshConfigured; + + public RefreshCapability(CommonInterface handler, ScheduledExecutorService scheduler, int refreshInterval) { + super(handler); + this.scheduler = scheduler; + this.dataValidity = Duration.ofSeconds(Math.max(0, refreshInterval)); + this.refreshConfigured = !probing(); + freeJobAndReschedule(2); + } + + @Override + public void dispose() { + freeJobAndReschedule(0); + super.dispose(); + } + + @Override + public void expireData() { + dataTimeStamp = Instant.now().minus(dataValidity); + freeJobAndReschedule(1); + } + + private Duration dataAge() { + return Duration.between(dataTimeStamp, Instant.now()); + } + + private boolean probing() { + return dataValidity.getSeconds() <= 0; + } + + private void proceedWithUpdate() { + handler.proceedWithUpdate(); + long delay; + if (!ThingStatus.ONLINE.equals(handler.getThing().getStatus())) { + logger.debug("Module is not ONLINE; special refresh interval is used"); + delay = OFFLINE_INTERVAL.toSeconds(); + if (probing()) { + dataTimeStamp0 = Instant.MIN; + } + } else if (refreshConfigured) { + delay = dataValidity.getSeconds(); + } else { + delay = (probing() ? PROBING_INTERVAL : dataValidity.minus(dataAge()).plus(DEFAULT_DELAY)).toSeconds(); + } + delay = delay < 2 ? PROBING_INTERVAL.toSeconds() : delay; + logger.debug("Module refreshed, next one in {} s", delay); + freeJobAndReschedule(delay); + } + + @Override + protected void updateNAThing(NAThing newData) { + super.updateNAThing(newData); + newData.getLastSeen().ifPresent(timestamp -> { + Instant tsInstant = timestamp.toInstant(); + if (probing()) { + if (Instant.MIN.equals(dataTimeStamp0)) { + dataTimeStamp0 = tsInstant; + logger.debug("First data timestamp is {}", dataTimeStamp0); + } else if (tsInstant.isAfter(dataTimeStamp0)) { + dataValidity = Duration.between(dataTimeStamp0, tsInstant); + logger.debug("Data validity period identified to be {}", dataValidity); + } else { + logger.debug("Data validity period not yet found - data timestamp unchanged"); + } + } + dataTimeStamp = tsInstant; + }); + } + + private void freeJobAndReschedule(long delay) { + refreshJob.ifPresent(job -> job.cancel(false)); + refreshJob = delay == 0 ? Optional.empty() + : Optional.of(scheduler.schedule(() -> proceedWithUpdate(), delay, TimeUnit.SECONDS)); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RestCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RestCapability.java new file mode 100644 index 0000000000000..2ac29b22342b4 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RestCapability.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.RestManager; +import org.openhab.binding.netatmo.internal.api.dto.Device; +import org.openhab.binding.netatmo.internal.api.dto.Module; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; + +/** + * The {@link RestCapability} is the base class for handler capabilities + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public abstract class RestCapability extends DeviceCapability { + private Optional api = Optional.empty(); + private Class restManagerClass; + + RestCapability(CommonInterface handler, Class restManagerClazz) { + super(handler); + this.restManagerClass = restManagerClazz; + } + + @Override + protected void updateNADevice(Device newData) { + super.updateNADevice(newData); + NAObjectMap modules = newData.getModules(); + handler.getActiveChildren().forEach(child -> { + Module childData = modules.get(child.getId()); + if (childData != null) { + child.setNewData(childData); + } + }); + } + + @Override + public final List updateReadings() { + List result = new ArrayList<>(); + getApi().ifPresent(api -> result.addAll(updateReadings(api))); + return result; + } + + protected List updateReadings(T api) { + return List.of(); + } + + protected Optional getApi() { + if (api.isEmpty()) { + ApiBridgeHandler bridgeApi = handler.getAccountHandler(); + if (bridgeApi != null) { + api = Optional.ofNullable(bridgeApi.getRestManager(restManagerClass)); + } + } + return api; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RoomCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RoomCapability.java new file mode 100644 index 0000000000000..cf1009c891f36 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/RoomCapability.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.commandToQuantity; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.action.RoomActions; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link RoomCapability} gives the ability to handle Room specifics + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class RoomCapability extends Capability { + private final Logger logger = LoggerFactory.getLogger(RoomCapability.class); + private Optional energyCapability = Optional.empty(); + + public RoomCapability(CommonInterface handler) { + super(handler); + } + + @Override + public void initialize() { + energyCapability = handler.getHomeCapability(EnergyCapability.class); + } + + @Override + public void handleCommand(String channelName, Command command) { + if (CHANNEL_SETPOINT_MODE.equals(channelName)) { + try { + SetpointMode targetMode = SetpointMode.valueOf(command.toString()); + if (targetMode == SetpointMode.MANUAL) { + logger.info("Switch to 'Manual' mode is done by setting a setpoint temp, command ignored"); + } else { + energyCapability.ifPresent(cap -> cap.setRoomThermMode(handler.getId(), targetMode)); + } + } catch (IllegalArgumentException e) { + logger.info("Command '{}' is not a valid setpoint mode for channel '{}'", command, channelName); + } + } else if (CHANNEL_VALUE.equals(channelName)) { + QuantityType quantity = commandToQuantity(command, MeasureClass.INSIDE_TEMPERATURE); + if (quantity != null) { + energyCapability.ifPresent(cap -> cap.setRoomThermTemp(handler.getId(), quantity.doubleValue())); + } else { + logger.warn("Incorrect command '{}' on channel '{}'", command, channelName); + } + } + } + + @Override + public Collection> getServices() { + return List.of(RoomActions.class); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java new file mode 100644 index 0000000000000..80e0c0fbfd4e8 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.SecurityApi; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode; +import org.openhab.binding.netatmo.internal.api.dto.HomeData; +import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule; +import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson; +import org.openhab.binding.netatmo.internal.api.dto.HomeEvent; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link SecurityCapability} is the base class for handler able to handle security features + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +class SecurityCapability extends RestCapability { + private final Logger logger = LoggerFactory.getLogger(SecurityCapability.class); + + SecurityCapability(CommonInterface handler) { + super(handler, SecurityApi.class); + } + + @Override + protected void updateHomeData(HomeData homeData) { + NAObjectMap persons = homeData.getPersons(); + NAObjectMap modules = homeData.getModules(); + handler.getActiveChildren().forEach(childHandler -> { + String childId = childHandler.getId(); + persons.getOpt(childId).ifPresentOrElse(person -> { + person.setIgnoredForThingUpdate(true); + childHandler.setNewData(person); + }, () -> { + modules.getOpt(childId).ifPresent(module -> { + module.setIgnoredForThingUpdate(true); + childHandler.setNewData(module); + }); + }); + }); + } + + @Override + protected void updateHomeStatus(HomeStatus homeStatus) { + NAObjectMap persons = homeStatus.getPersons(); + NAObjectMap modules = homeStatus.getModules(); + handler.getActiveChildren().forEach(childHandler -> { + String childId = childHandler.getId(); + persons.getOpt(childId).ifPresentOrElse(person -> childHandler.setNewData(person), () -> { + modules.getOpt(childId).ifPresentOrElse(module -> childHandler.setNewData(module), () -> { + // This module is not present in the homestatus data, so it is considered as unreachable + HomeStatusModule module = new HomeStatusModule(); + module.setReachable(false); + childHandler.setNewData(module); + }); + }); + }); + } + + @Override + protected void updateHomeEvent(HomeEvent homeEvent) { + String personId = homeEvent.getPersonId(); + if (personId != null) { + handler.getActiveChildren().stream().filter(handler -> personId.equals(handler.getId())).findFirst() + .ifPresent(handler -> { + homeEvent.setIgnoredForThingUpdate(true); + handler.setNewData(homeEvent); + }); + } + String cameraId = homeEvent.getCameraId(); + handler.getActiveChildren().stream().filter(handler -> cameraId.equals(handler.getId())).findFirst() + .ifPresent(handler -> { + homeEvent.setIgnoredForThingUpdate(true); + handler.setNewData(homeEvent); + }); + } + + public Collection getCameraEvents(String cameraId) { + return getApi().map(api -> { + try { + return api.getCameraEvents(handler.getId(), cameraId); + } catch (NetatmoException e) { + logger.warn("Error retrieving last events of camera '{}' : {}", cameraId, e.getMessage()); + return null; + } + }).orElse(List.of()); + } + + public Collection getPersonEvents(String personId) { + return getApi().map(api -> { + try { + return api.getPersonEvents(handler.getId(), personId); + } catch (NetatmoException e) { + logger.warn("Error retrieving last events of person '{}' : {}", personId, e.getMessage()); + return null; + } + }).orElse(List.of()); + } + + public void setPersonAway(String personId, boolean away) { + getApi().ifPresent(api -> { + try { + api.setPersonAwayStatus(handler.getId(), personId, away); + handler.expireData(); + } catch (NetatmoException e) { + logger.warn("Error setting person away/at home '{}' : {}", personId, e.getMessage()); + } + }); + } + + public @Nullable String ping(String vpnUrl) { + return getApi().map(api -> { + try { + return api.ping(vpnUrl); + } catch (NetatmoException e) { + logger.warn("Error pinging camera '{}' : {}", vpnUrl, e.getMessage()); + return null; + } + }).orElse(null); + } + + public void changeStatus(@Nullable String localURL, boolean status) { + if (localURL == null) { + logger.info("Monitoring changes can only be done on local camera."); + return; + } + getApi().ifPresent(api -> { + try { + api.changeStatus(localURL, status); + handler.expireData(); + } catch (NetatmoException e) { + logger.warn("Error changing camera monitoring status '{}' : {}", status, e.getMessage()); + } + }); + } + + public void changeFloodlightMode(@Nullable String localURL, FloodLightMode mode) { + if (localURL == null) { + logger.info("Changing floodlight mode can only be done on local camera."); + return; + } + getApi().ifPresent(api -> { + try { + api.changeFloodLightMode(localURL, mode); + handler.expireData(); + } catch (NetatmoException e) { + logger.warn("Error changing Presence floodlight mode '{}' : {}", mode, e.getMessage()); + } + }); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/WeatherCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/WeatherCapability.java new file mode 100644 index 0000000000000..b5ddb315f30e8 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/WeatherCapability.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.capability; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.WeatherApi; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.handler.CommonInterface; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link WeatherCapability} give the ability to read weather station api + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class WeatherCapability extends RestCapability { + private final Logger logger = LoggerFactory.getLogger(WeatherCapability.class); + + public WeatherCapability(CommonInterface handler) { + super(handler, WeatherApi.class); + } + + @Override + protected List updateReadings(WeatherApi api) { + try { + return List.of(api.getStationData(handler.getId())); + } catch (NetatmoException e) { + logger.warn("Error retrieving weather data '{}' : {}", handler.getId(), e.getMessage()); + } + return List.of(); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityChannelHelper.java new file mode 100644 index 0000000000000..7895d76bede5c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityChannelHelper.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.types.State; + +/** + * The {@link AirQualityChannelHelper} handles specific channels of things handling ppm measurement + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class AirQualityChannelHelper extends ChannelHelper { + + public AirQualityChannelHelper() { + this(GROUP_AIR_QUALITY); + } + + protected AirQualityChannelHelper(String groupName) { + super(groupName, MeasureClass.CO2); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + return CHANNEL_CO2.equals(channelId) ? toQuantityType(dashboard.getCo2(), MeasureClass.CO2) : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityExtChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityExtChannelHelper.java new file mode 100644 index 0000000000000..7cf618f16b394 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/AirQualityExtChannelHelper.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.types.State; + +/** + * The {@link AirQualityExtChannelHelper} handles specific channels of NHC thing. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class AirQualityExtChannelHelper extends AirQualityChannelHelper { + + public AirQualityExtChannelHelper() { + super(GROUP_TYPE_AIR_QUALITY_EXTENDED); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + return CHANNEL_HEALTH_INDEX.equals(channelId) ? new DecimalType(dashboard.getHealthIdx()) + : super.internalGetDashboard(channelId, dashboard); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryChannelHelper.java new file mode 100644 index 0000000000000..beaf93ed90a9b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryChannelHelper.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; +import org.openhab.binding.netatmo.internal.api.dto.Module; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.types.State; + +/** + * The {@link BatteryChannelHelper} handles specific channels of modules using batteries + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class BatteryChannelHelper extends ChannelHelper { + + public BatteryChannelHelper() { + super(GROUP_BATTERY); + } + + protected BatteryChannelHelper(String groupName) { + super(groupName); + } + + @Override + protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { + int percent = -1; + if (naThing instanceof Module) { + percent = ((Module) naThing).getBatteryPercent(); + } + if (naThing instanceof HomeStatusModule) { + percent = ((HomeStatusModule) naThing).getBatteryState().level; + } + switch (channelId) { + case CHANNEL_VALUE: + if (percent >= 0) { + return new DecimalType(percent); + } + case CHANNEL_LOW_BATTERY: + if (percent >= 0) { + return OnOffType.from(percent < 20); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryExtChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryExtChannelHelper.java new file mode 100644 index 0000000000000..645601bb77be7 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/BatteryExtChannelHelper.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; +import org.openhab.binding.netatmo.internal.api.dto.Module; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.types.State; + +/** + * The {@link BatteryExtChannelHelper} handles specific channels of modules using batteries + * having battery status information available on top of standard information + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class BatteryExtChannelHelper extends BatteryChannelHelper { + + public BatteryExtChannelHelper() { + super(GROUP_TYPE_BATTERY_EXTENDED); + } + + @Override + protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { + if (CHANNEL_BATTERY_STATUS.equals(channelId)) { + if (naThing instanceof Module) { + return toStringType(((Module) naThing).getBatteryState()); + } + if (naThing instanceof HomeStatusModule) { + return toStringType(((HomeStatusModule) naThing).getBatteryState()); + } + } + return super.internalGetProperty(channelId, naThing, config); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java new file mode 100644 index 0000000000000..db73b2dee2786 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link CameraChannelHelper} handles specific channels of cameras + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class CameraChannelHelper extends ChannelHelper { + private static final String QUALITY_CONF_ENTRY = "quality"; + private static final String LIVE_PICTURE = "/live/snapshot_720.jpg"; + private boolean isLocal; + private @Nullable String vpnUrl; + private @Nullable String localUrl; + + public CameraChannelHelper() { + super(GROUP_CAM_STATUS, GROUP_CAM_LIVE); + } + + public void setUrls(String vpnUrl, @Nullable String localUrl) { + this.localUrl = localUrl; + this.vpnUrl = vpnUrl; + this.isLocal = localUrl != null; + } + + public @Nullable String getLocalURL() { + return localUrl; + } + + @Override + protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { + if (naThing instanceof HomeStatusModule) { + HomeStatusModule camera = (HomeStatusModule) naThing; + boolean isMonitoring = OnOffType.ON.equals(camera.getMonitoring()); + switch (channelId) { + case CHANNEL_MONITORING: + return camera.getMonitoring(); + case CHANNEL_SD_CARD: + return toStringType(camera.getSdStatus()); + case CHANNEL_ALIM_STATUS: + return toStringType(camera.getAlimStatus()); + case CHANNEL_LIVEPICTURE_VPN_URL: + return toStringType(getLivePictureURL(false, isMonitoring)); + case CHANNEL_LIVEPICTURE_LOCAL_URL: + return toStringType(getLivePictureURL(true, isMonitoring)); + case CHANNEL_LIVEPICTURE: + return toRawType(getLivePictureURL(isLocal, isMonitoring)); + case CHANNEL_LIVESTREAM_VPN_URL: + return getLiveStreamURL(false, (String) config.get(QUALITY_CONF_ENTRY), isMonitoring); + case CHANNEL_LIVESTREAM_LOCAL_URL: + return getLiveStreamURL(true, (String) config.get(QUALITY_CONF_ENTRY), isMonitoring); + } + } + return null; + } + + private @Nullable String getLivePictureURL(boolean local, boolean isMonitoring) { + String url = local ? localUrl : vpnUrl; + if (!isMonitoring || (local && !isLocal) || url == null) { + return null; + } + return String.format("%s%s", url, LIVE_PICTURE); + } + + private State getLiveStreamURL(boolean local, @Nullable String configQual, boolean isMonitoring) { + String url = local ? localUrl : vpnUrl; + if (!isMonitoring || (local && !isLocal) || url == null) { + return UnDefType.NULL; + } + String finalQual = configQual != null ? configQual : "poor"; + return toStringType("%s/live/%s", url, local ? String.format("files/%s/index.m3u8", finalQual) : "index.m3u8"); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/ChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/ChannelHelper.java new file mode 100644 index 0000000000000..a5c9adbef073f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/ChannelHelper.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.binding.netatmo.internal.api.dto.Event; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.providers.NetatmoThingTypeProvider; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.types.State; + +/** + * The {@link ChannelHelper} is the base class for all channel helpers. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public abstract class ChannelHelper { + private @Nullable NAObject data; + private final Set channelGroupTypes; + private final Set channelGroups = new HashSet<>(); + private Set extensibleChannels = Set.of(); + + ChannelHelper(String... providedGroups) { + this.channelGroupTypes = Set.of(providedGroups); + channelGroupTypes.forEach(groupType -> channelGroups.add(NetatmoThingTypeProvider.toGroupName(groupType))); + } + + ChannelHelper(String providedGroup, MeasureClass measureClass) { + this(providedGroup); + this.extensibleChannels = measureClass.channels.keySet(); + } + + public void setNewData(@Nullable NAObject data) { + this.data = data; + } + + public final @Nullable State getChannelState(String channelId, @Nullable String groupId, Configuration config) { + State result = null; + if (channelGroups.isEmpty() || (groupId != null && channelGroups.contains(groupId))) { + NAObject localData = data; + if (localData instanceof Event) { + result = internalGetEvent(channelId, (Event) localData); + if (result != null) { + return result; + } + } + if (localData instanceof NAThing) { + NAThing naThing = (NAThing) localData; + result = internalGetProperty(channelId, naThing, config); + if (result != null) { + return result; + } + Dashboard dashboard = naThing.getDashboardData(); + if (dashboard != null) { + result = internalGetDashboard(channelId, dashboard); + if (result != null) { + return result; + } + } + } + if (localData instanceof NAObject) { + result = internalGetObject(channelId, localData); + if (result != null) { + return result; + } + } + result = internalGetOther(channelId); + } + return result; + } + + protected @Nullable State internalGetObject(String channelId, NAObject localData) { + return null; + } + + protected @Nullable State internalGetOther(String channelId) { + return null; + } + + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + return null; + } + + protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { + return null; + } + + protected @Nullable State internalGetEvent(String channelId, Event event) { + return null; + } + + public Set getChannelGroupTypes() { + return channelGroupTypes; + } + + public Set getExtensibleChannels() { + return extensibleChannels; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventChannelHelper.java new file mode 100644 index 0000000000000..2ac950ba19153 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventChannelHelper.java @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.binding.netatmo.internal.api.dto.Event; +import org.openhab.binding.netatmo.internal.api.dto.HomeEvent; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link EventChannelHelper} handles specific channels of cameras + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class EventChannelHelper extends ChannelHelper { + private boolean isLocal; + private @Nullable ZonedDateTime lastEventTime; + private @Nullable String vpnUrl, localUrl; + private ModuleType moduleType = ModuleType.UNKNOWN; + + public EventChannelHelper() { + this(GROUP_LAST_EVENT); + } + + protected EventChannelHelper(String groupName) { + super(groupName); + } + + public void setModuleType(ModuleType moduleType) { + this.moduleType = moduleType; + } + + public void setUrls(String vpnUrl, @Nullable String localUrl) { + this.localUrl = localUrl; + this.vpnUrl = vpnUrl; + this.isLocal = localUrl != null; + } + + @Override + public void setNewData(@Nullable NAObject data) { + if (data instanceof Event) { + Event event = (Event) data; + ZonedDateTime localLast = lastEventTime; + ZonedDateTime eventTime = event.getTime(); + if ((localLast != null && !eventTime.isAfter(localLast)) || !event.getEventType().appliesOn(moduleType)) { + return; // ignore incoming events if they are deprecated + } + lastEventTime = eventTime; + } + super.setNewData(data); + } + + @Override + protected @Nullable State internalGetEvent(String channelId, Event event) { + switch (channelId) { + case CHANNEL_EVENT_TYPE: + return toStringType(event.getEventType()); + case CHANNEL_EVENT_MESSAGE: + return toStringType(event.getName()); + case CHANNEL_EVENT_TIME: + return new DateTimeType(event.getTime()); + case CHANNEL_EVENT_PERSON_ID: + return toStringType(event.getPersonId()); + case CHANNEL_EVENT_CAMERA_ID: + return toStringType(event.getCameraId()); + case CHANNEL_EVENT_SUBTYPE: + return event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL); + case CHANNEL_EVENT_SNAPSHOT: + return toRawType(event.getSnapshotUrl()); + case CHANNEL_EVENT_SNAPSHOT_URL: + return toStringType(event.getSnapshotUrl()); + } + if (event instanceof HomeEvent) { + HomeEvent homeEvent = (HomeEvent) event; + switch (channelId) { + case CHANNEL_EVENT_VIDEO_STATUS: + return homeEvent.getVideoId() != null ? toStringType(homeEvent.getVideoStatus()) : UnDefType.NULL; + case CHANNEL_EVENT_VIDEO_LOCAL_URL: + return getStreamURL(true, homeEvent.getVideoId()); + case CHANNEL_EVENT_VIDEO_VPN_URL: + return getStreamURL(false, homeEvent.getVideoId()); + } + } + return null; + } + + private State getStreamURL(boolean local, @Nullable String videoId) { + String url = local ? localUrl : vpnUrl; + if ((local && !isLocal) || url == null || videoId == null) { + return UnDefType.NULL; + } + return toStringType("%s/vod/%s/index.m3u8", url, videoId); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventPersonChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventPersonChannelHelper.java new file mode 100644 index 0000000000000..ff4054be66724 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventPersonChannelHelper.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.EventType; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.binding.netatmo.internal.api.dto.Event; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.types.State; + +/** + * The {@link EventPersonChannelHelper} handles specific channels of person events + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class EventPersonChannelHelper extends EventChannelHelper { + public EventPersonChannelHelper() { + super(GROUP_PERSON_EVENT); + } + + @Override + protected @Nullable State internalGetEvent(String channelId, Event event) { + EventType eventType = event.getEventType(); + if (eventType.appliesOn(ModuleType.PERSON) && CHANNEL_PERSON_AT_HOME.equals(channelId)) { + return OnOffType.from(EventType.PERSON.equals(eventType) || EventType.PERSON_HOME.equals(eventType)); + } + return super.internalGetEvent(channelId, event); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeEnergyChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeEnergyChannelHelper.java new file mode 100644 index 0000000000000..f594c1e61558e --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeEnergyChannelHelper.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import java.time.DayOfWeek; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode; +import org.openhab.binding.netatmo.internal.api.dto.HomeData; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.binding.netatmo.internal.api.dto.ThermProgram; +import org.openhab.binding.netatmo.internal.api.dto.TimeTableItem; +import org.openhab.binding.netatmo.internal.api.dto.Zone; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link HomeEnergyChannelHelper} handles specific channels of thermostat settings at home level. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HomeEnergyChannelHelper extends ChannelHelper { + + public HomeEnergyChannelHelper() { + super(GROUP_ENERGY); + } + + @Override + protected @Nullable State internalGetProperty(String channelId, NAThing data, Configuration config) { + if (data instanceof HomeData) { + HomeData homeData = (HomeData) data; + SetpointMode thermMode = homeData.getThermMode(); + ThermProgram currentProgram = homeData.getActiveProgram(); + switch (channelId) { + case CHANNEL_SETPOINT_DURATION: + return toQuantityType(homeData.getThermSetpointDefaultDuration(), Units.MINUTE); + case CHANNEL_PLANNING: + return (currentProgram != null ? toStringType(currentProgram.getName()) : null); + case CHANNEL_SETPOINT_END_TIME: + switch (thermMode) { + case PROGRAM: + case HOME: + case SCHEDULE: + return currentProgram != null ? toDateTimeType(nextProgramTime(currentProgram)) + : UnDefType.UNDEF; + default: + return UnDefType.UNDEF; + } + case CHANNEL_SETPOINT_MODE: + switch (thermMode) { + case OFF: + case MAX: + case UNKNOWN: + return UnDefType.UNDEF; + case PROGRAM: + case HOME: + case SCHEDULE: + if (currentProgram != null) { + TimeTableItem currentProgramMode = currentProgramMode(currentProgram); + if (currentProgramMode != null) { + Zone zone = currentProgram.getZone(String.valueOf(currentProgramMode.getZoneId())); + if (zone != null) { + return new StringType(zone.getName()); + } + } + } + return UnDefType.NULL; + default: + return toStringType(thermMode); + } + } + } + return null; + } + + private static ZonedDateTime programBaseTimeZdt() { + return ZonedDateTime.now().with(DayOfWeek.MONDAY).truncatedTo(ChronoUnit.DAYS); + } + + private static long minutesSinceProgramBaseTime() { + return ChronoUnit.MINUTES.between(programBaseTimeZdt(), ZonedDateTime.now()); + } + + private static @Nullable TimeTableItem currentProgramMode(ThermProgram activeProgram) { + long diff = minutesSinceProgramBaseTime(); + return activeProgram.getTimetable().stream().filter(t -> t.getMinuteOffset() < diff) + .reduce((first, second) -> second).orElse(null); + } + + private static ZonedDateTime nextProgramTime(ThermProgram activeProgram) { + long diff = minutesSinceProgramBaseTime(); + // By default we'll use the first slot of next week - this case will be true if + // we are in the last schedule of the week so below loop will not exit by break + List timetable = activeProgram.getTimetable(); + int next = timetable.get(0).getMinuteOffset() + (7 * 24 * 60); + for (TimeTableItem timeTable : timetable) { + if (timeTable.getMinuteOffset() > diff) { + next = timeTable.getMinuteOffset(); + break; + } + } + return programBaseTimeZdt().plusMinutes(next); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeSecurityChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeSecurityChannelHelper.java new file mode 100644 index 0000000000000..81140ced67ddc --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HomeSecurityChannelHelper.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toRawType; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.HomeData; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson; +import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link HomeSecurityChannelHelper} handles specific information for security purpose. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HomeSecurityChannelHelper extends ChannelHelper { + private long persons = -1; + private long unknowns = -1; + private @Nullable String unknownSnapshot; + private List knownIds = List.of(); + + public HomeSecurityChannelHelper() { + super(GROUP_SECURITY); + } + + @Override + public void setNewData(@Nullable NAObject data) { + super.setNewData(data); + if (data instanceof HomeData) { + HomeData homeData = (HomeData) data; + knownIds = homeData.getPersons().values().stream().filter(person -> person.isKnown()).map(p -> p.getId()) + .collect(Collectors.toList()); + } + if (data instanceof HomeStatus) { + HomeStatus status = (HomeStatus) data; + NAObjectMap allPersons = status.getPersons(); + List present = allPersons.values().stream().filter(p -> !p.isOutOfSight()) + .collect(Collectors.toList()); + + persons = present.size(); + unknowns = present.stream().filter(person -> !knownIds.contains(person.getId())).count(); + } + } + + @Override + protected @Nullable State internalGetOther(String channelId) { + switch (channelId) { + case CHANNEL_PERSON_COUNT: + return persons != -1 ? new DecimalType(persons) : UnDefType.NULL; + case CHANNEL_UNKNOWN_PERSON_COUNT: + return unknowns != -1 ? new DecimalType(unknowns) : UnDefType.NULL; + case CHANNEL_UNKNOWN_PERSON_PICTURE: + return unknownSnapshot != null ? toRawType(unknownSnapshot) : UnDefType.NULL; + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HumidityChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HumidityChannelHelper.java new file mode 100644 index 0000000000000..09ba0f7f7c0d8 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/HumidityChannelHelper.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; +import static org.openhab.binding.netatmo.internal.utils.WeatherUtils.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.types.State; + +/** + * The {@link HumidityChannelHelper} handles specific channels of modules returning humidity measures. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class HumidityChannelHelper extends ChannelHelper { + + public HumidityChannelHelper() { + super(GROUP_HUMIDITY, MeasureClass.HUMIDITY); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + switch (channelId) { + case CHANNEL_HUMIDEX: + return new DecimalType(humidex(dashboard.getTemperature(), dashboard.getHumidity())); + case CHANNEL_HUMIDEX_SCALE: + return new DecimalType(humidexScale(humidex(dashboard.getTemperature(), dashboard.getHumidity()))); + case CHANNEL_VALUE: + return toQuantityType(dashboard.getHumidity(), MeasureClass.HUMIDITY); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/LocationChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/LocationChannelHelper.java new file mode 100644 index 0000000000000..1ece7b4cbb8eb --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/LocationChannelHelper.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.Device; +import org.openhab.binding.netatmo.internal.api.dto.Home; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link LocationChannelHelper} handles specific channels of modules holding a location data + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class LocationChannelHelper extends ChannelHelper { + + public LocationChannelHelper() { + super(GROUP_LOCATION); + } + + @Override + protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { + if (CHANNEL_VALUE.equals(channelId)) { + State point = UnDefType.UNDEF; + if (naThing instanceof Home) { + point = ((Home) naThing).getLocation(); + } else if (naThing instanceof Device) { + point = ((Device) naThing).getPlace().map(place -> place.getLocation()).orElse(point); + } + return point; + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/MeasuresChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/MeasuresChannelHelper.java new file mode 100644 index 0000000000000..f04ca9856c4ff --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/MeasuresChannelHelper.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.types.State; + +/** + * The {@link MeasuresChannelHelper} handles extensible channels based on getMeasure endpoint. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@NonNullByDefault +public class MeasuresChannelHelper extends ChannelHelper { + private @Nullable Map measures; + + public void setMeasures(Map measures) { + this.measures = measures; + } + + @Override + protected @Nullable State internalGetOther(String channelId) { + Map localMeasures = measures; + if (localMeasures != null) { + return localMeasures.get(channelId); + } + throw new IllegalArgumentException("localMeasures should not be null, please file a bug report."); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/NoiseChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/NoiseChannelHelper.java new file mode 100644 index 0000000000000..d0d4a60563327 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/NoiseChannelHelper.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.types.State; + +/** + * The {@link NoiseChannelHelper} handles specific channels of modules measuring sound level + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class NoiseChannelHelper extends ChannelHelper { + + public NoiseChannelHelper() { + super(GROUP_NOISE, MeasureClass.NOISE); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + return CHANNEL_VALUE.equals(channelId) ? toQuantityType(dashboard.getNoise(), MeasureClass.NOISE) : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PersonChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PersonChannelHelper.java new file mode 100644 index 0000000000000..c730f290c21e3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PersonChannelHelper.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.types.State; + +/** + * The {@link PersonChannelHelper} handles channels of person things. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PersonChannelHelper extends ChannelHelper { + + public PersonChannelHelper() { + super(GROUP_PERSON); + } + + @Override + protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { + if (naThing instanceof HomeDataPerson) { + HomeDataPerson person = (HomeDataPerson) naThing; + switch (channelId) { + case CHANNEL_PERSON_AVATAR_URL: + return toStringType(person.getUrl().orElse(null)); + case CHANNEL_PERSON_AVATAR: + return toRawType(person.getUrl().orElse(null)); + } + } + if (naThing instanceof HomeStatusPerson) { + HomeStatusPerson person = (HomeStatusPerson) naThing; + switch (channelId) { + case CHANNEL_PERSON_AT_HOME: + return OnOffType.from(!person.isOutOfSight()); + case CHANNEL_LAST_SEEN: + return toDateTimeType(person.getLastSeen()); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PresenceChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PresenceChannelHelper.java new file mode 100644 index 0000000000000..c2449c95391d7 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PresenceChannelHelper.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.types.State; + +/** + * The {@link PresenceChannelHelper} handles specific channels of Presence external cameras + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PresenceChannelHelper extends ChannelHelper { + public PresenceChannelHelper() { + super(GROUP_PRESENCE); + } + + @Override + protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { + if (naThing instanceof HomeStatusModule) { + HomeStatusModule camera = (HomeStatusModule) naThing; + switch (channelId) { + case CHANNEL_FLOODLIGHT: + return toStringType(camera.getFloodlight()); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureChannelHelper.java new file mode 100644 index 0000000000000..a2432f01584d4 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureChannelHelper.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.types.State; + +/** + * The {@link PressureChannelHelper} handles specific channels of modules measuring pressure + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PressureChannelHelper extends ChannelHelper { + + public PressureChannelHelper() { + this(GROUP_PRESSURE); + } + + protected PressureChannelHelper(String groupName) { + super(groupName, MeasureClass.PRESSURE); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + switch (channelId) { + case CHANNEL_VALUE: + return toQuantityType(dashboard.getPressure(), MeasureClass.PRESSURE); + case CHANNEL_ABSOLUTE_PRESSURE: + return toQuantityType(dashboard.getAbsolutePressure(), MeasureClass.PRESSURE); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureExtChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureExtChannelHelper.java new file mode 100644 index 0000000000000..3df7bd9b118cb --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PressureExtChannelHelper.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.types.State; + +/** + * The {@link PressureExtChannelHelper} handles specific behavior of modules measuring pressure + * with pressure trend capability + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class PressureExtChannelHelper extends PressureChannelHelper { + + public PressureExtChannelHelper() { + super(GROUP_TYPE_PRESSURE_EXTENDED); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + return channelId.equals(CHANNEL_TREND) ? toStringType(dashboard.getPressureTrend()) + : super.internalGetDashboard(channelId, dashboard); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RainChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RainChannelHelper.java new file mode 100644 index 0000000000000..08521bf65c005 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RainChannelHelper.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.types.State; + +/** + * The {@link RainChannelHelper} handles specific channels of modules measuring rain + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class RainChannelHelper extends ChannelHelper { + + public RainChannelHelper() { + super(GROUP_RAIN, MeasureClass.RAIN_QUANTITY); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + switch (channelId) { + case CHANNEL_VALUE: + return toQuantityType(dashboard.getRain(), MeasureClass.RAIN_INTENSITY); + case CHANNEL_SUM_RAIN1: + return toQuantityType(dashboard.getSumRain1(), MeasureClass.RAIN_QUANTITY); + case CHANNEL_SUM_RAIN24: + return toQuantityType(dashboard.getSumRain24(), MeasureClass.RAIN_QUANTITY); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RoomChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RoomChannelHelper.java new file mode 100644 index 0000000000000..2914d2e920c9f --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/RoomChannelHelper.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.api.dto.Room; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; + +/** + * The {@link RoomChannelHelper} handles specific channels of the room + * + * @author Markus Dillmann - Initial contribution + * + */ +@NonNullByDefault +public class RoomChannelHelper extends ChannelHelper { + + public RoomChannelHelper() { + super(GROUP_ROOM_PROPERTIES, GROUP_ROOM_TEMPERATURE); + } + + @Override + protected @Nullable State internalGetObject(String channelId, NAObject naObject) { + if (naObject instanceof Room) { + Room room = (Room) naObject; + switch (channelId) { + case CHANNEL_ROOM_WINDOW_OPEN: + return room.hasOpenedWindows(); + case CHANNEL_ANTICIPATING: + return room.isAnticipating(); + case CHANNEL_ROOM_HEATING_POWER: + return toQuantityType(room.getHeatingPowerRequest(), Units.PERCENT); + case CHANNEL_VALUE: + return toQuantityType(room.getMeasuredTemp(), MeasureClass.INSIDE_TEMPERATURE); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SetpointChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SetpointChannelHelper.java new file mode 100644 index 0000000000000..56d9f8744fc0c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SetpointChannelHelper.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.NAObject; +import org.openhab.binding.netatmo.internal.api.dto.Room; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link SetpointChannelHelper} handles channels for a room capable of managing a thermostat + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class SetpointChannelHelper extends ChannelHelper { + + public SetpointChannelHelper() { + super(GROUP_TH_SETPOINT); + } + + @Override + protected @Nullable State internalGetObject(String channelId, NAObject naObject) { + if (naObject instanceof Room) { + Room room = (Room) naObject; + switch (channelId) { + case CHANNEL_SETPOINT_MODE: + return toStringType(room.getSetpointMode().name()); + case CHANNEL_SETPOINT_START_TIME: + return toDateTimeType(room.getSetpointBegin()); + case CHANNEL_SETPOINT_END_TIME: + return toDateTimeType(room.getSetpointEnd()); + case CHANNEL_VALUE: + switch (room.getSetpointMode()) { + case OFF: + case MAX: + return UnDefType.UNDEF; + case AWAY: + case HOME: + case MANUAL: + case SCHEDULE: + case FROST_GUARD: + case PROGRAM: + return toQuantityType(room.getSetpointTemp(), MeasureClass.INSIDE_TEMPERATURE); + case UNKNOWN: + return UnDefType.NULL; + } + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SignalChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SignalChannelHelper.java new file mode 100644 index 0000000000000..13715ac1a9211 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SignalChannelHelper.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; + +/** + * The {@link SignalChannelHelper} handles specific behavior of WIFI or RF devices and modules + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class SignalChannelHelper extends ChannelHelper { + + public SignalChannelHelper() { + super(GROUP_SIGNAL); + } + + @Override + protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { + int status = naThing.getRadioStatus(); + if (status != -1) { + switch (channelId) { + case CHANNEL_SIGNAL_STRENGTH: + return new DecimalType(getSignalStrength(status, naThing.getType().getSignalLevels())); + case CHANNEL_VALUE: + return toQuantityType(status, Units.DECIBEL_MILLIWATTS); + } + } + return null; + } + + private int getSignalStrength(int signalLevel, int[] levels) { + int level; + for (level = 0; level < levels.length; level++) { + if (signalLevel > levels[level]) { + break; + } + } + return level; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureChannelHelper.java new file mode 100644 index 0000000000000..bef052b6cb268 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureChannelHelper.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; +import static org.openhab.binding.netatmo.internal.utils.WeatherUtils.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.types.State; + +/** + * The {@link TemperatureChannelHelper} handles channels of modules measuring temperature + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class TemperatureChannelHelper extends ChannelHelper { + /* + * TemperatureChannelHelper may be used by indoor or outdoor modules. There is no easy way here to decide what is + * the handler owning the channelHelper. The usage of OUTSIDE_TEMPERATURE instead of INSIDE_TEMPERATURE is by design + * because OUTSIDE_TEMPERATURE has wide value range than INSIDE_TEMPERATURE. + */ + public TemperatureChannelHelper() { + this(GROUP_TEMPERATURE, MeasureClass.OUTSIDE_TEMPERATURE); + } + + protected TemperatureChannelHelper(String groupName, MeasureClass measureClass) { + super(groupName, measureClass); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + switch (channelId) { + case CHANNEL_VALUE: + return toQuantityType(dashboard.getTemperature(), MeasureClass.OUTSIDE_TEMPERATURE); + case CHANNEL_MIN_VALUE: + return toQuantityType(dashboard.getMinTemp(), MeasureClass.OUTSIDE_TEMPERATURE); + case CHANNEL_MAX_VALUE: + return toQuantityType(dashboard.getMaxTemp(), MeasureClass.OUTSIDE_TEMPERATURE); + case CHANNEL_MIN_TIME: + return toDateTimeType(dashboard.getDateMinTemp()); + case CHANNEL_MAX_TIME: + return toDateTimeType(dashboard.getDateMaxTemp()); + case CHANNEL_HEAT_INDEX: + return toQuantityType(heatIndex(dashboard.getTemperature(), dashboard.getHumidity()), + MeasureClass.HEAT_INDEX); + case CHANNEL_DEWPOINT: + return toQuantityType(dewPoint(dashboard.getTemperature(), dashboard.getHumidity()), + MeasureClass.OUTSIDE_TEMPERATURE); + case CHANNEL_DEWPOINT_DEP: + double dewPoint = dewPoint(dashboard.getTemperature(), dashboard.getHumidity()); + return toQuantityType(dewPointDep(dashboard.getTemperature(), dewPoint), + MeasureClass.OUTSIDE_TEMPERATURE); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureExtChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureExtChannelHelper.java new file mode 100644 index 0000000000000..b97a1546d55c6 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureExtChannelHelper.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.types.State; + +/** + * The {@link TemperatureExtChannelHelper} handles specific channels of modules measuring temperature + * with temp trend capability + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class TemperatureExtChannelHelper extends TemperatureChannelHelper { + public TemperatureExtChannelHelper() { + super(GROUP_TYPE_TEMPERATURE_EXTENDED, MeasureClass.INSIDE_TEMPERATURE); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + return CHANNEL_TREND.equals(channelId) ? toStringType(dashboard.getTempTrend()) + : super.internalGetDashboard(channelId, dashboard); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureOutChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureOutChannelHelper.java new file mode 100644 index 0000000000000..af00c75151bcd --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TemperatureOutChannelHelper.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.types.State; + +/** + * The {@link TemperatureOutChannelHelper} handles specific channels of modules measuring temperature + * with temp trend capability + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class TemperatureOutChannelHelper extends TemperatureChannelHelper { + public TemperatureOutChannelHelper() { + super(GROUP_TYPE_TEMPERATURE_OUTSIDE, MeasureClass.OUTSIDE_TEMPERATURE); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + return CHANNEL_TREND.equals(channelId) ? toStringType(dashboard.getTempTrend()) + : super.internalGetDashboard(channelId, dashboard); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/Therm1ChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/Therm1ChannelHelper.java new file mode 100644 index 0000000000000..5cb2a2bfd9c1c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/Therm1ChannelHelper.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.types.State; + +/** + * The {@link Therm1ChannelHelper} handles specific behavior of the thermostat module + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class Therm1ChannelHelper extends ChannelHelper { + + public Therm1ChannelHelper() { + super(GROUP_TH_PROPERTIES); + } + + @Override + protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { + return (naThing instanceof HomeStatusModule && CHANNEL_THERM_RELAY.equals(channelId)) + ? ((HomeStatusModule) naThing).getBoilerStatus() + : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampChannelHelper.java new file mode 100644 index 0000000000000..8c8dbba725578 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampChannelHelper.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toDateTimeType; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.NAThing; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.types.State; + +/** + * The {@link TimestampChannelHelper} handles specific behavior + * of modules reporting last seen + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class TimestampChannelHelper extends ChannelHelper { + + public TimestampChannelHelper() { + this(GROUP_TIMESTAMP); + } + + protected TimestampChannelHelper(String groupName) { + super(groupName); + } + + @Override + protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { + Optional lastSeen = naThing.getLastSeen(); + return CHANNEL_LAST_SEEN.equals(channelId) && lastSeen.isPresent() ? toDateTimeType(lastSeen) : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampExtChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampExtChannelHelper.java new file mode 100644 index 0000000000000..e3e614ed2f9ff --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/TimestampExtChannelHelper.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toDateTimeType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.types.State; + +/** + * The {@link TimestampExtChannelHelper} handles specific behavior + * of modules reporting measurement timestamp in dashboard + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class TimestampExtChannelHelper extends TimestampChannelHelper { + + public TimestampExtChannelHelper() { + super(GROUP_TYPE_TIMESTAMP_EXTENDED); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + return CHANNEL_MEASURES_TIMESTAMP.equals(channelId) ? toDateTimeType(dashboard.getTimeUtc()) : null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/WindChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/WindChannelHelper.java new file mode 100644 index 0000000000000..8ab2b7bbda430 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/WindChannelHelper.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.handler.channelhelper; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.binding.netatmo.internal.api.dto.Dashboard; +import org.openhab.core.types.State; + +/** + * The {@link WindChannelHelper} handle specifics channels of modules measuring wind + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class WindChannelHelper extends ChannelHelper { + + public WindChannelHelper() { + super(GROUP_WIND); + } + + @Override + protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) { + switch (channelId) { + case CHANNEL_WIND_ANGLE: + return toQuantityType(dashboard.getWindAngle(), MeasureClass.WIND_ANGLE); + case CHANNEL_WIND_STRENGTH: + return toQuantityType(dashboard.getWindStrength(), MeasureClass.WIND_SPEED); + case CHANNEL_GUST_ANGLE: + return toQuantityType(dashboard.getGustAngle(), MeasureClass.WIND_ANGLE); + case CHANNEL_GUST_STRENGTH: + return toQuantityType(dashboard.getGustStrength(), MeasureClass.WIND_SPEED); + case CHANNEL_MAX_WIND_STRENGTH: + return toQuantityType(dashboard.getMaxWindStr(), MeasureClass.WIND_SPEED); + case CHANNEL_DATE_MAX_WIND_STRENGTH: + return toDateTimeType(dashboard.getDateMaxWindStr()); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/homecoach/NAHealthyHomeCoachHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/homecoach/NAHealthyHomeCoachHandler.java deleted file mode 100644 index 9c7c543f7a70b..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.homecoach; - -import static org.openhab.binding.netatmo.internal.APIUtils.*; -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.State; - -import io.swagger.client.model.NADashboardData; -import io.swagger.client.model.NAHealthyHomeCoach; - -/** - * {@link NAHealthyHomeCoachHandler} is the class used to handle the Health Home Coach device - * - * @author Michael Svinth - Initial contribution OH2 version - * - */ -@NonNullByDefault -public class NAHealthyHomeCoachHandler extends NetatmoDeviceHandler { - - 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 7475e97af8b75..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.presence; - -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.toOnOffType; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.netatmo.internal.camera.CameraHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; - -import io.swagger.client.model.NAWelcomeCamera; - -/** - * {@link NAPresenceCameraHandler} is the class used to handle Presence camera data - * - * @author Sven Strohschein - Initial contribution - */ -@NonNullByDefault -public class NAPresenceCameraHandler extends CameraHandler { - - private static final String FLOODLIGHT_SET_URL_PATH = "/command/floodlight_set_config"; - - private State floodlightAutoModeState = UnDefType.UNDEF; - - public NAPresenceCameraHandler(final Thing thing, final TimeZoneProvider timeZoneProvider) { - super(thing, timeZoneProvider); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - String channelId = channelUID.getId(); - switch (channelId) { - case CHANNEL_CAMERA_FLOODLIGHT: - if (command == OnOffType.ON) { - switchFloodlight(true); - } else if (command == OnOffType.OFF) { - switchFloodlight(false); - } - break; - case CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE: - if (command == OnOffType.ON) { - switchFloodlightAutoMode(true); - } else if (command == OnOffType.OFF) { - switchFloodlightAutoMode(false); - } - break; - } - super.handleCommand(channelUID, command); - } - - @Override - protected State getNAThingProperty(String channelId) { - switch (channelId) { - case CHANNEL_CAMERA_FLOODLIGHT: - return getFloodlightState(); - case CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE: - // The auto-mode state shouldn't be updated, because this isn't a dedicated information. When the - // floodlight is switched on the state within the Netatmo API is "on" and the information if the - // previous - // state was "auto" instead of "off" is lost... Therefore the binding handles its own auto-mode state. - if (floodlightAutoModeState == UnDefType.UNDEF) { - floodlightAutoModeState = getFloodlightAutoModeState(); - } - return floodlightAutoModeState; - } - return super.getNAThingProperty(channelId); - } - - private State getFloodlightState() { - return getModule().map(m -> toOnOffType(m.getLightModeStatus() == NAWelcomeCamera.LightModeStatusEnum.ON)) - .orElse(UnDefType.UNDEF); - } - - private State getFloodlightAutoModeState() { - return getModule().map(m -> toOnOffType(m.getLightModeStatus() == NAWelcomeCamera.LightModeStatusEnum.AUTO)) - .orElse(UnDefType.UNDEF); - } - - private void switchFloodlight(boolean isOn) { - if (isOn) { - changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.ON); - } else { - switchFloodlightAutoMode(floodlightAutoModeState == OnOffType.ON); - } - } - - private void switchFloodlightAutoMode(boolean isAutoMode) { - floodlightAutoModeState = toOnOffType(isAutoMode); - if (isAutoMode) { - changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.AUTO); - } else { - changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.OFF); - } - } - - private void changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum mode) { - Optional localCameraURL = getLocalCameraURL(); - if (localCameraURL.isPresent()) { - String url = localCameraURL.get() + FLOODLIGHT_SET_URL_PATH + "?config=%7B%22mode%22:%22" + mode.toString() - + "%22%7D"; - executeGETRequest(url); - - invalidateParentCacheAndRefresh(); - } - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoChannelTypeProvider.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoChannelTypeProvider.java new file mode 100644 index 0000000000000..42fe411ce496c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoChannelTypeProvider.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.providers; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeBuilder; +import org.openhab.core.thing.type.ChannelTypeProvider; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.thing.type.StateChannelTypeBuilder; +import org.osgi.service.component.annotations.Component; + +/** + * Extends the ChannelTypeProvider generating Channel Types based on {@link MeasureClass} enum. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +@Component(service = { NetatmoChannelTypeProvider.class, ChannelTypeProvider.class }) +public class NetatmoChannelTypeProvider implements ChannelTypeProvider { + private final Collection channelTypes = new HashSet<>(); + + public NetatmoChannelTypeProvider() { + MeasureClass.AS_SET.forEach(mc -> mc.channels.forEach((measureChannel, channelDetails) -> { + StateChannelTypeBuilder channelTypeBuilder = ChannelTypeBuilder + .state(new ChannelTypeUID(BINDING_ID, measureChannel), measureChannel.replace("-", " "), + channelDetails.itemType) + .withStateDescriptionFragment(channelDetails.stateDescriptionFragment) + .withConfigDescriptionURI(channelDetails.configURI); + + channelTypes.add(channelTypeBuilder.build()); + })); + } + + @Override + public Collection getChannelTypes(@Nullable Locale locale) { + return Collections.unmodifiableCollection(channelTypes); + } + + @Override + public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) { + return channelTypes.stream().filter(ct -> ct.getUID().equals(channelTypeUID)).findFirst().orElse(null); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NATherm1StateDescriptionProvider.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoDescriptionProvider.java similarity index 71% 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/providers/NetatmoDescriptionProvider.java index bd46b3188b3c2..ee1b14f14df87 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/providers/NetatmoDescriptionProvider.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.netatmo.internal; +package org.openhab.binding.netatmo.internal.providers; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.events.EventPublisher; @@ -23,18 +23,16 @@ 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, // + public NetatmoDescriptionProvider(final @Reference EventPublisher eventPublisher, + final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { this.eventPublisher = eventPublisher; this.itemChannelLinkRegistry = itemChannelLinkRegistry; diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoThingTypeProvider.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoThingTypeProvider.java new file mode 100644 index 0000000000000..0e81eb687e32c --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoThingTypeProvider.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.providers; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; + +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.ModuleType; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingTypeProvider; +import org.openhab.core.thing.i18n.ThingTypeI18nLocalizationService; +import org.openhab.core.thing.type.ChannelGroupDefinition; +import org.openhab.core.thing.type.ChannelGroupTypeUID; +import org.openhab.core.thing.type.ThingType; +import org.openhab.core.thing.type.ThingTypeBuilder; +import org.osgi.framework.Bundle; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Extends the ThingTypeProvider to generated Thing types based on {@link ModuleType} enum. + * + * @author Gaël L'hopital - Initial contribution + * + */ + +@Component(service = ThingTypeProvider.class) +@NonNullByDefault +public class NetatmoThingTypeProvider implements ThingTypeProvider { + private final Logger logger = LoggerFactory.getLogger(NetatmoThingTypeProvider.class); + private final ThingTypeI18nLocalizationService localizationService; + private final Bundle bundle; + + @Activate + public NetatmoThingTypeProvider(final @Reference ThingTypeI18nLocalizationService localizationService, + ComponentContext componentContext) { + this.bundle = componentContext.getBundleContext().getBundle(); + this.localizationService = localizationService; + } + + @Override + public Collection getThingTypes(@Nullable Locale locale) { + return ModuleType.AS_SET.stream().filter(mt -> mt != ModuleType.UNKNOWN) + .map(mt -> Optional.ofNullable(getThingType(mt.thingTypeUID, locale))).map(Optional::get) + .collect(Collectors.toList()); + } + + @Override + public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) { + if (BINDING_ID.equalsIgnoreCase(thingTypeUID.getBindingId())) { + try { + ModuleType moduleType = ModuleType.from(thingTypeUID); + + ThingTypeBuilder thingTypeBuilder = ThingTypeBuilder.instance(thingTypeUID, thingTypeUID.toString()) + .withRepresentationProperty(EQUIPMENT_ID).withExtensibleChannelTypeIds(moduleType.extensions) + .withChannelGroupDefinitions(getGroupDefinitions(moduleType)) + .withConfigDescriptionURI(moduleType.getConfigDescription()); + + ThingTypeUID bridgeType = moduleType.getBridge().thingTypeUID; + if (!ModuleType.UNKNOWN.thingTypeUID.equals(bridgeType)) { + thingTypeBuilder.withSupportedBridgeTypeUIDs(List.of(bridgeType.getAsString())); + } + + return localizationService.createLocalizedThingType(bundle, + moduleType.isABridge() ? thingTypeBuilder.buildBridge() : thingTypeBuilder.build(), locale); + } catch (IllegalArgumentException e) { + logger.warn("Unable to define ModuleType for thingType {} : {}", thingTypeUID.getId(), e.getMessage()); + } + } + return null; + } + + private List getGroupDefinitions(ModuleType thingType) { + return thingType.groupTypes.stream().map(groupTypeName -> new ChannelGroupDefinition(toGroupName(groupTypeName), + new ChannelGroupTypeUID(BINDING_ID, groupTypeName))).collect(Collectors.toList()); + } + + public static String toGroupName(String groupeTypeName) { + return groupeTypeName.replace(OPTION_EXTENDED, "").replace(OPTION_OUTSIDE, ""); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAMainHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/station/NAMainHandler.java deleted file mode 100644 index bf9d1c6ac1ed8..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.station; - -import static org.openhab.binding.netatmo.internal.APIUtils.*; -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.netatmo.internal.WeatherUtils; -import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler; -import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.State; - -import io.swagger.client.model.NADashboardData; -import io.swagger.client.model.NAMain; - -/** - * {@link NAMainHandler} is the base class for all current Netatmo - * weather station equipments (both modules and devices) - * - * @author Gaël L'hopital - Initial contribution - * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules - * - */ -@NonNullByDefault -public class NAMainHandler extends NetatmoDeviceHandler { - 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 0279b66f84cf9..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.station; - -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.netatmo.internal.WeatherUtils; -import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.State; - -import io.swagger.client.model.NADashboardData; -import io.swagger.client.model.NAStationModule; - -/** - * {@link NAModule1Handler} is the class used to handle the outdoor module - * capable of reporting temperature and humidity - * - * @author Gaël L'hopital - Initial contribution - * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules - * - */ -@NonNullByDefault -public class NAModule1Handler extends NetatmoModuleHandler { - 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 82f8ffe3083e9..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.station; - -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.State; - -import io.swagger.client.model.NADashboardData; -import io.swagger.client.model.NAStationModule; - -/** - * {@link NAModule2Handler} is the class used to handle the wind module - * capable of reporting wind angle and strength - * - * @author Gaël L'hopital - Initial contribution - */ -@NonNullByDefault -public class NAModule2Handler extends NetatmoModuleHandler { - - 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 654d5525edcbb..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.station; - -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.State; - -import io.swagger.client.model.NADashboardData; -import io.swagger.client.model.NAStationModule; - -/** - * {@link NAModule3Handler} is the class used to handle the Rain Gauge - * capable of measuring precipitation - * - * @author Gaël L'hopital - Initial contribution - * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules - * - */ -@NonNullByDefault -public class NAModule3Handler extends NetatmoModuleHandler { - 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 6699b6a37acb3..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.station; - -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.netatmo.internal.WeatherUtils; -import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.State; - -import io.swagger.client.model.NADashboardData; -import io.swagger.client.model.NAStationModule; - -/** - * {@link NAModule4Handler} is the class used to handle the additional - * indoor module capable of reporting temperature, humidity and CO2 level - * - * @author Gaël L'hopital - Initial contribution - * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules - * - */ -@NonNullByDefault -public class NAModule4Handler extends NetatmoModuleHandler { - 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 66b1ea2a79da5..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.thermostat; - -import static org.openhab.binding.netatmo.internal.APIUtils.*; -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.time.ZonedDateTime; -import java.time.temporal.TemporalAdjusters; -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; - -import io.swagger.client.model.NAPlug; -import io.swagger.client.model.NAYearMonth; - -/** - * {@link NAPlugHandler} is the class used to handle the plug - * device of a thermostat set - * - * @author Gaël L'hopital - Initial contribution OH2 version - * - */ -@NonNullByDefault -public class NAPlugHandler extends NetatmoDeviceHandler { - - 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 748cb938b8266..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.thermostat; - -import static org.openhab.binding.netatmo.internal.APIUtils.*; -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; - -import javax.measure.quantity.Temperature; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.NATherm1StateDescriptionProvider; -import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler; -import org.openhab.core.config.core.Configuration; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; -import org.openhab.core.types.StateOption; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.swagger.client.api.ThermostatApi; -import io.swagger.client.model.NASetpoint; -import io.swagger.client.model.NAThermProgram; -import io.swagger.client.model.NAThermostat; -import io.swagger.client.model.NATimeTableItem; -import io.swagger.client.model.NAZone; - -/** - * {@link NATherm1Handler} is the class used to handle the thermostat - * module of a thermostat set - * - * @author Gaël L'hopital - Initial contribution OH2 version - * - */ -@NonNullByDefault -public class NATherm1Handler extends NetatmoModuleHandler { - 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/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..10f66999d957a --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/ChannelTypeUtils.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.ZonedDateTime; +import java.util.Optional; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Measure; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.core.io.net.http.HttpUtil; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.RawType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * This class holds various channel values conversion methods + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class ChannelTypeUtils { + + public static @Nullable QuantityType commandToQuantity(Command command, MeasureClass measureClass) { + Measure measureDef = measureClass.measureDefinition; + if (command instanceof QuantityType) { + return ((QuantityType) command).toUnit(measureDef.unit); + } + try { + double value = Double.parseDouble(command.toString()); + return QuantityType.valueOf(value, measureDef.unit); + } catch (NumberFormatException ignore) { + } + return null; + } + + public static State toStringType(String pattern, Object... parms) { + return toStringType(String.format(pattern, parms)); + } + + public static State toStringType(@Nullable Enum value) { + return (value == null) ? UnDefType.NULL : new StringType(value.name()); + } + + public static State toStringType(@Nullable String value) { + return (value == null) ? UnDefType.NULL : new StringType(value); + } + + public static State toDateTimeType(@Nullable ZonedDateTime zonedDateTime) { + return (zonedDateTime == null) ? UnDefType.NULL : new DateTimeType(zonedDateTime); + } + + public static State toDateTimeType(Optional zonedDateTime) { + return zonedDateTime.map(zdt -> (State) new DateTimeType(zdt)).orElse(UnDefType.NULL); + } + + public static State toQuantityType(@Nullable Double value, @Nullable MeasureClass measureClass) { + if (value != null && !value.isNaN()) { + if (measureClass != null) { + Measure measureDef = measureClass.measureDefinition; + BigDecimal measure = new BigDecimal(Math.min(measureDef.maxValue, Math.max(measureDef.minValue, value))) + .setScale(measureDef.scale, RoundingMode.HALF_UP); + return new QuantityType<>(measure, measureDef.unit); + } else { + return new DecimalType(value); + } + } + return UnDefType.NULL; + } + + public static State toQuantityType(@Nullable Number value, Unit unit) { + return value == null ? UnDefType.NULL : new QuantityType<>(value, unit); + } + + public static State toRawType(@Nullable String pictureUrl) { + if (pictureUrl != null) { + RawType picture = HttpUtil.downloadImage(pictureUrl); + if (picture != null) { + return picture; + } + } + return UnDefType.UNDEF; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/WeatherUtils.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/WeatherUtils.java similarity index 77% 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 42376f92045f9..8240eb0cacc93 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; @@ -31,7 +31,7 @@ public class WeatherUtils { * @param humidity relative level (%) * @return heatIndex in (°C) */ - public static double getHeatIndex(double temperature, double humidity) { + public static double heatIndex(double temperature, double humidity) { double tempF = (temperature * 9.0 / 5.0) + 32.0; // calculations are done in Fahrenheit double heatIndex; if (tempF >= 80.0) { @@ -51,7 +51,7 @@ public static double getHeatIndex(double temperature, double humidity) { return (heatIndex - 32) * 5.0 / 9.0; // convert back to Celsius } - public static double getDewPointDep(double temperature, double dewpoint) { + public static double dewPointDep(double temperature, double dewpoint) { return temperature - dewpoint; } @@ -64,7 +64,7 @@ public static double getDewPointDep(double temperature, double dewpoint) { * @param humidity relative level (%) * @return dewpoint temperature */ - public static double getDewPoint(double temperature, double humidity) { + public static double dewPoint(double temperature, double humidity) { double a = 17.271, b = 237.2; double gamma = ((a * temperature) / (b + temperature)) + Math.log(humidity / 100.0); return b * gamma / (a - gamma); @@ -73,14 +73,23 @@ 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 */ - public static double getHumidex(double temperature, double hygro) { + public static double humidex(double temperature, double hygro) { double result = 6.112 * Math.pow(10, 7.5 * temperature / (237.7 + temperature)) * hygro / 100; - result = temperature + 0.555555556 * (result - 10); - return result; + return temperature + 0.555555556 * (result - 10); + } + + /** + * Compute the associated scale appreciation of a given humidex index + * https://www.researchgate.net/figure/The-scale-of-Humidex-and-the-degree-of-comfort_tbl1_335293174 + * + * @param Humidex index value + * @return scale between 0 and 4 + */ + public static int humidexScale(double humidex) { + return humidex < 30 ? 0 : humidex < 40 ? 1 : humidex < 45 ? 2 : humidex < 55 ? 3 : 4; } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NAWebhookCameraEvent.java deleted file mode 100644 index b9456c3484fc1..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.webhook; - -import java.util.ArrayList; -import java.util.List; - -import com.google.gson.annotations.SerializedName; - -/** - * The {@link NAWebhookCameraEvent} is responsible to hold - * data given back by the Netatmo API when calling the webhook - * - * @author Gaël L'hopital - Initial contribution - * - */ -public class NAWebhookCameraEvent { - - public enum AppTypeEnum { - @SerializedName("app_camera") - CAMERA("camera"); - - private String value; - - AppTypeEnum(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - } - - @SerializedName("app_type") - private AppTypeEnum appType = null; - - public AppTypeEnum getAppType() { - return appType; - } - - public enum EventTypeEnum { - @SerializedName("person") - PERSON("person"), - - @SerializedName("person_away") - PERSON_AWAY("person_away"), - - @SerializedName("movement") - MOVEMENT("movement"), - - @SerializedName("outdoor") - OUTDOOR("outdoor"), - - @SerializedName("connection") - CONNECTION("connection"), - - @SerializedName("disconnection") - DISCONNECTION("disconnection"), - - @SerializedName("on") - ON("on"), - - @SerializedName("off") - OFF("off"), - - @SerializedName("boot") - BOOT("boot"), - - @SerializedName("sd") - SD("sd"), - - @SerializedName("alim") - ALIM("alim"), - - @SerializedName("daily_summary") - DAILY_SUMMARY("daily_summary"), - - @SerializedName("new_module") - NEW_MODULE("new_module"), - - @SerializedName("module_connect") - MODULE_CONNECT("module_connect"), - - @SerializedName("module_disconnect") - MODULE_DISCONNECT("module_disconnect"), - - @SerializedName("module_low_battery") - MODULE_LOW_BATTERY("module_low_battery"), - - @SerializedName("module_end_update") - MODULE_END_UPDATE("module_end_update"), - - @SerializedName("tag_big_move") - TAG_BIG_MOVE("tag_big_move"), - - @SerializedName("tag_small_move") - TAG_SMALL_MOVE("tag_small_move"), - - @SerializedName("tag_uninstalled") - TAG_UNINSTALLED("tag_uninstalled"), - - @SerializedName("tag_open") - TAG_OPEN("tag_open"); - - private String value; - - EventTypeEnum(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - } - - @SerializedName("event_type") - private EventTypeEnum eventType = null; - - public EventTypeEnum getEventType() { - return eventType; - } - - @SerializedName("camera_id") - String cameraId; - - public String getCameraId() { - return cameraId; - } - - @SerializedName("home_id") - String homeId; - - public String getHomeId() { - return homeId; - } - - @SerializedName("persons") - private List 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 39d1ebe1dc1ea..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.webhook; - -import com.google.gson.annotations.SerializedName; - -/** - * The {@link NAWebhookCameraEventPerson} is responsible to hold - * data given back by the Netatmo API when calling the webhook - * - * @author Gaël L'hopital - Initial contribution - * - */ -public class NAWebhookCameraEventPerson { - @SerializedName("id") - String id; - - public String getId() { - return id; - } - - @SerializedName("face_id") - String faceId; - - public String getFaceId() { - return faceId; - } - - @SerializedName("face_key") - String faceKey; - - public String getFaceKey() { - return faceKey; - } - - @SerializedName("is_known") - Boolean isKnown; - - public Boolean isKnown() { - return isKnown; - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NetatmoServlet.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NetatmoServlet.java new file mode 100644 index 0000000000000..ffd091afe8d87 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/NetatmoServlet.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.webhook; + +import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Scanner; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriBuilderException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.SecurityApi; +import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent; +import org.openhab.binding.netatmo.internal.deserialization.NADeserializer; +import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; +import org.openhab.binding.netatmo.internal.handler.capability.EventCapability; +import org.osgi.service.http.HttpService; +import org.osgi.service.http.NamespaceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * HTTP servlet for Netatmo Webhook. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class NetatmoServlet extends HttpServlet { + private static final long serialVersionUID = -354583910860541214L; + private static final String CALLBACK_URI = "/" + BINDING_ID; + + private final Logger logger = LoggerFactory.getLogger(NetatmoServlet.class); + private final Map dataListeners = new ConcurrentHashMap<>(); + private final HttpService httpService; + private final NADeserializer deserializer; + private final Optional securityApi; + private boolean hookSet = false; + + public NetatmoServlet(HttpService httpService, ApiBridgeHandler apiBridge, String webHookUrl) { + this.httpService = httpService; + this.deserializer = apiBridge.getDeserializer(); + this.securityApi = Optional.ofNullable(apiBridge.getRestManager(SecurityApi.class)); + securityApi.ifPresent(api -> { + try { + httpService.registerServlet(CALLBACK_URI, this, null, httpService.createDefaultHttpContext()); + logger.debug("Started Netatmo Webhook Servlet at '{}'", CALLBACK_URI); + URI uri = UriBuilder.fromUri(webHookUrl).path(BINDING_ID).build(); + try { + logger.info("Setting Netatmo Welcome WebHook to {}", uri.toString()); + api.addwebhook(uri); + hookSet = true; + } catch (UriBuilderException e) { + logger.info("webhookUrl is not a valid URI '{}' : {}", uri, e.getMessage()); + } catch (NetatmoException e) { + logger.info("Error setting webhook : {}", e.getMessage()); + } + } catch (ServletException | NamespaceException e) { + logger.warn("Could not start Netatmo Webhook Servlet : {}", e.getMessage()); + } + }); + } + + public void dispose() { + securityApi.ifPresent(api -> { + if (hookSet) { + logger.info("Releasing Netatmo Welcome WebHook"); + try { + api.dropWebhook(); + } catch (NetatmoException e) { + logger.warn("Error releasing webhook : {}", e.getMessage()); + } + } + httpService.unregister(CALLBACK_URI); + }); + logger.debug("Netatmo Webhook Servlet stopped"); + } + + @Override + protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException { + if (req != null && resp != null) { + String data = inputStreamToString(req.getInputStream()); + if (!data.isEmpty()) { + logger.debug("Event transmitted from restService : {}", data); + try { + WebhookEvent event = deserializer.deserialize(WebhookEvent.class, data); + List tobeNotified = collectNotified(event); + dataListeners.keySet().stream().filter(tobeNotified::contains).forEach(id -> { + EventCapability module = dataListeners.get(id); + if (module != null) { + module.setNewData(event); + } + }); + } catch (NetatmoException e) { + logger.info("Error deserializing webhook data received : {}. {}", data, e.getMessage()); + } + } + resp.setCharacterEncoding(StandardCharsets.UTF_8.name()); + resp.setContentType(MediaType.APPLICATION_JSON); + resp.setHeader("Access-Control-Allow-Origin", "*"); + resp.setHeader("Access-Control-Allow-Methods", HttpMethod.POST); + resp.setIntHeader("Access-Control-Max-Age", 3600); + resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + resp.getWriter().write(""); + } + } + + private List collectNotified(WebhookEvent event) { + List result = new ArrayList<>(); + result.add(event.getCameraId()); + String person = event.getPersonId(); + if (person != null) { + result.add(person); + } + result.addAll(event.getPersons().keySet()); + return result.stream().distinct().collect(Collectors.toList()); + } + + public void registerDataListener(String id, EventCapability dataListener) { + dataListeners.put(id, dataListener); + } + + public void unregisterDataListener(EventCapability dataListener) { + dataListeners.entrySet().removeIf(entry -> entry.getValue().equals(dataListener)); + } + + private String inputStreamToString(InputStream is) throws IOException { + String value = ""; + try (Scanner scanner = new Scanner(is)) { + scanner.useDelimiter("\\A"); + value = scanner.hasNext() ? scanner.next() : ""; + } + return value; + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/WelcomeWebHookServlet.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/webhook/WelcomeWebHookServlet.java deleted file mode 100644 index bd395344a2a6e..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.webhook; - -import java.io.IOException; -import java.util.Objects; -import java.util.Scanner; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler; -import org.osgi.service.http.HttpService; -import org.osgi.service.http.NamespaceException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; - -/** - * Main OSGi service and HTTP servlet for Netatmo Welcome Webhook. - * - * @author Gaël L'hopital - Initial contribution - */ -@NonNullByDefault -public class WelcomeWebHookServlet extends HttpServlet { - private static final long serialVersionUID = 1288539782077957954L; - private static final String PATH = "/netatmo/%s/camera"; - private static final String APPLICATION_JSON = "application/json"; - private static final String CHARSET = "utf-8"; - - private final Gson gson = new Gson(); - - private final Logger logger = LoggerFactory.getLogger(WelcomeWebHookServlet.class); - - private HttpService httpService; - private @Nullable NetatmoBridgeHandler bridgeHandler; - private String path; - - public WelcomeWebHookServlet(HttpService httpService, String id) { - this.httpService = httpService; - this.path = String.format(PATH, id); - } - - /** - * OSGi activation callback. - * - * @param config Service config. - */ - public void activate(NetatmoBridgeHandler bridgeHandler) { - this.bridgeHandler = bridgeHandler; - try { - httpService.registerServlet(path, this, null, httpService.createDefaultHttpContext()); - logger.debug("Started Netatmo Webhook servlet at {}", path); - } catch (ServletException | NamespaceException e) { - logger.error("Could not start Netatmo Webhook servlet: {}", e.getMessage(), e); - } - } - - /** - * OSGi deactivation callback. - */ - public void deactivate() { - httpService.unregister(path); - logger.debug("Netatmo webhook servlet stopped"); - this.bridgeHandler = null; - } - - @Override - protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) - throws ServletException, IOException { - if (req == null || resp == null) { - return; - } - - String data = inputStreamToString(req); - NetatmoBridgeHandler handler = bridgeHandler; - if (!data.isEmpty() && handler != null) { - NAWebhookCameraEvent event = gson.fromJson(data, NAWebhookCameraEvent.class); - logger.debug("Event transmitted from restService"); - handler.webHookEvent(Objects.requireNonNull(event)); - } - - setHeaders(resp); - resp.getWriter().write(""); - } - - private String inputStreamToString(HttpServletRequest req) throws IOException { - String value = ""; - try (Scanner scanner = new Scanner(req.getInputStream())) { - scanner.useDelimiter("\\A"); - value = scanner.hasNext() ? scanner.next() : ""; - } - return value; - } - - private void setHeaders(HttpServletResponse response) { - response.setCharacterEncoding(CHARSET); - response.setContentType(APPLICATION_JSON); - response.setHeader("Access-Control-Allow-Origin", "*"); - response.setHeader("Access-Control-Allow-Methods", "POST"); - response.setHeader("Access-Control-Max-Age", "3600"); - response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); - } - - public String getPath() { - return path; - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeCameraHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeCameraHandler.java deleted file mode 100644 index 0a6f8d28f9820..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.welcome; - -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.netatmo.internal.camera.CameraHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.State; - -/** - * {@link NAWelcomeCameraHandler} is the class used to handle the Welcome Camera Data - * - * @author Ing. Peter Weiss - Initial contribution - * - */ -@NonNullByDefault -public class NAWelcomeCameraHandler extends CameraHandler { - - public NAWelcomeCameraHandler(Thing thing, final TimeZoneProvider timeZoneProvider) { - super(thing, timeZoneProvider); - } - - @Override - protected State getNAThingProperty(String channelId) { - switch (channelId) { - case CHANNEL_WELCOME_CAMERA_STATUS: - return getStatusState(); - case CHANNEL_WELCOME_CAMERA_SDSTATUS: - return getSdStatusState(); - case CHANNEL_WELCOME_CAMERA_ALIMSTATUS: - return getAlimStatusState(); - case CHANNEL_WELCOME_CAMERA_ISLOCAL: - return getIsLocalState(); - case CHANNEL_WELCOME_CAMERA_LIVEPICTURE_URL: - return getLivePictureURLState(); - case CHANNEL_WELCOME_CAMERA_LIVEPICTURE: - return getLivePictureState(); - case CHANNEL_WELCOME_CAMERA_LIVESTREAM_URL: - return getLiveStreamState(); - } - return super.getNAThingProperty(channelId); - } -} diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/welcome/NAWelcomeHomeHandler.java deleted file mode 100644 index 6363baba3d028..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.welcome; - -import static org.openhab.binding.netatmo.internal.APIUtils.*; -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.Calendar; -import java.util.Comparator; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; -import java.util.function.Function; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.ChannelTypeUtils; -import org.openhab.binding.netatmo.internal.camera.CameraHandler; -import org.openhab.binding.netatmo.internal.handler.AbstractNetatmoThingHandler; -import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler; -import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.swagger.client.model.NAWelcomeEvent; -import io.swagger.client.model.NAWelcomeHome; -import io.swagger.client.model.NAWelcomePlace; -import io.swagger.client.model.NAWelcomeSnapshot; -import io.swagger.client.model.NAWelcomeSubEvent; - -/** - * {@link NAWelcomeHomeHandler} is the class used to handle the Welcome Home Data - * - * @author Gaël L'hopital - Initial contribution - * @author Ing. Peter Weiss - Welcome camera implementation - * - */ -@NonNullByDefault -public class NAWelcomeHomeHandler extends NetatmoDeviceHandler { - 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 c0a61e32832a6..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.welcome; - -import static org.openhab.binding.netatmo.internal.APIUtils.*; -import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*; -import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; - -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler; -import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; - -import io.swagger.client.api.WelcomeApi; -import io.swagger.client.model.NAWelcomeEvent; -import io.swagger.client.model.NAWelcomeEventResponse; -import io.swagger.client.model.NAWelcomeFace; -import io.swagger.client.model.NAWelcomePerson; - -/** - * {@link NAWelcomePersonHandler} is the class used to handle the Welcome Home Data - * - * @author Ing. Peter Weiss - Initial contribution - * - */ -@NonNullByDefault -public class NAWelcomePersonHandler extends NetatmoModuleHandler { - 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..976fa7a65e82a 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 @@ -7,11 +7,24 @@ The Netatmo binding integrates Weather Station with companion modules, Healthy Home Coach, Thermostat Plug and Welcome Camera. + - - - 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. + + + Defines the set of capabilities you want to operate on. + + + + + + + true + WEATHER + + + + + For Weather Stations: A friend gave you access to their Netatmo Weather Station. 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..e7023f6ce00d6 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,7 +5,7 @@ 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 @@ -13,146 +13,132 @@ - Client Secret provided for the application you created + Client Secret provided for the application you created. password - Your Netatmo API username (email) + Your Netatmo API username (email). - Your Netatmo API password + 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 - - - + - Protocol, public IP and port to access OH2 server from Internet. - true + Protocol, public IP and port to access openHAB server from Internet. The reconnection interval to Netatmo API (in s). - 5400 - true + 300 - - - - - Id of the Device (mac address) + + + + Observation period for summing rain quantities. + + + + + + + + + 1week - - - - Id of the Device (mac address) + + + + Defines the requested boundary. + + + + + MIN + + + + Observation period for searched boundary. + + + + + + + + + 1week - - - - Id of the Module - - - - - Id of the main device + + + + Defines the requested boundary. + + + + + DATE_MIN + + + + Observation period for the queried value. + + + + + 1week - + - - Id of the Module - - - - - Id of the main device + + ID of the device (MAC address). + - - - Default duration of thermostat change when force to max or manual. - 60 - true + + + + Unique identifier of the thing defined by Netatmo. - + - - UUID of the home + + Unique identifier of the thing defined by Netatmo. - + - The refresh interval to poll Netatmo API (in ms). - 300000 - true + The refresh interval to poll Netatmo API (in seconds). + 180 - + - - Camera MAC Address - - - - - UUID of the home hosting the camera - - - - - - - UUID of the Person + + ID of the device (MAC address). - - - UUID of the home + + + The refresh interval to poll Netatmo API (in seconds). + 180 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..cc52514f7d4e3 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties @@ -0,0 +1,351 @@ +# binding + +binding.netatmo.name = Netatmo Binding +binding.netatmo.description = The Netatmo binding integrates Weather Station with companion modules, Healthy Home Coach, Thermostat Plug and Welcome Camera. + +# binding config + +binding.config.netatmo.features.label = Feature Area +binding.config.netatmo.features.description = Defines the set of capabilities you want to operate on. +binding.config.netatmo.features.option.AIR_CARE = Air Care +binding.config.netatmo.features.option.WEATHER = Weather +binding.config.netatmo.features.option.ENERGY = Energy +binding.config.netatmo.features.option.SECURITY = Security +binding.config.netatmo.readFriends.label = Access Guests +binding.config.netatmo.readFriends.description = For Weather Stations: A friend gave you access to their Netatmo Weather Station. + +# channel group types + +channel-group-type.netatmo.airquality-extended.label = Air Quality +channel-group-type.netatmo.airquality.label = Air Quality +channel-group-type.netatmo.battery-extended.label = Battery +channel-group-type.netatmo.battery.label = Battery +channel-group-type.netatmo.energy.label = Home Energy +channel-group-type.netatmo.energy.channel.end.label = Mode End +channel-group-type.netatmo.energy.channel.end.description = End time of the currently applied setpoint. +channel-group-type.netatmo.humidity.label = Humidity +channel-group-type.netatmo.last-event.label = Last Event +channel-group-type.netatmo.last-event.channel.local-video-url.label = Video Local URL +channel-group-type.netatmo.last-event.channel.local-video-url.description = Local URL of the event recording. +channel-group-type.netatmo.last-event.channel.time.label = Event Timestamp +channel-group-type.netatmo.last-event.channel.time.description = Moment when event occurred. +channel-group-type.netatmo.last-event.channel.vpn-video-url.label = Video VPN URL +channel-group-type.netatmo.last-event.channel.vpn-video-url.description = URL of the event recording through Netatmo VPN. +channel-group-type.netatmo.live.label = Live Monitoring +channel-group-type.netatmo.live.channel.local-picture-url.label = Live Snapshot Local URL +channel-group-type.netatmo.live.channel.local-picture-url.description = Local URL of the live snapshot for this camera. +channel-group-type.netatmo.live.channel.local-stream-url.label = Live Stream Local URL +channel-group-type.netatmo.live.channel.local-stream-url.description = Local URL of the live stream for this camera. +channel-group-type.netatmo.live.channel.vpn-picture-url.label = Live Snapshot VPN URL +channel-group-type.netatmo.live.channel.vpn-picture-url.description = URL of the live snapshot for this camera through Netatmo VPN. +channel-group-type.netatmo.live.channel.vpn-stream-url.label = Live Stream VPN URL +channel-group-type.netatmo.live.channel.vpn-stream-url.description = URL of the live stream for this camera through Netatmo VPN. +channel-group-type.netatmo.location.label = Location +channel-group-type.netatmo.noise.label = Noise +channel-group-type.netatmo.person-event.label = Last Event +channel-group-type.netatmo.person-event.channel.message.description = Last event message from this person. +channel-group-type.netatmo.person-event.channel.snapshot.description = Picture of the last event for this person. +channel-group-type.netatmo.person-event.channel.snapshot-url.description = URL for the picture of the last event for this person. +channel-group-type.netatmo.person-event.channel.time.label = Person Timestamp +channel-group-type.netatmo.person-event.channel.time.description = Moment of the last event for this person. +channel-group-type.netatmo.person.label = Person +channel-group-type.netatmo.person.channel.last-seen.label = Last Seen +channel-group-type.netatmo.person.channel.last-seen.description = Moment when this person was last seen. +channel-group-type.netatmo.plug.label = Thermostat Plug +channel-group-type.netatmo.presence.label = Presence Camera +channel-group-type.netatmo.pressure-extended.label = Pressure +channel-group-type.netatmo.pressure-extended.channel.trend.label = Pressure Trend +channel-group-type.netatmo.pressure.label = Pressure +channel-group-type.netatmo.rain.label = Rain gauge +channel-group-type.netatmo.rain.channel.sum-1.label = Rain 1h +channel-group-type.netatmo.rain.channel.sum-1.description = Quantity of water over last hour. +channel-group-type.netatmo.rain.channel.sum-24.label = Rain 24h +channel-group-type.netatmo.rain.channel.sum-24.description = Quantity of water during the current day. +channel-group-type.netatmo.room-properties.label = Room Status +channel-group-type.netatmo.room-temperature.label = Room Temperature +channel-group-type.netatmo.security.label = Home Security +channel-group-type.netatmo.setpoint.label = Setpoint +channel-group-type.netatmo.setpoint.channel.end.label = Setpoint End +channel-group-type.netatmo.setpoint.channel.end.description = End time of the currently applied setpoint. +channel-group-type.netatmo.setpoint.channel.start.label = Setpoint Start +channel-group-type.netatmo.setpoint.channel.start.description = Start time of the currently applied setpoint. +channel-group-type.netatmo.signal.label = Signal +channel-group-type.netatmo.status.label = Camera Status +channel-group-type.netatmo.temperature-extended.label = Temperature +channel-group-type.netatmo.temperature-extended.channel.max-time.label = Today Max Timestamp +channel-group-type.netatmo.temperature-extended.channel.max-time.description = Moment when temperature was measured at its maximum today. +channel-group-type.netatmo.temperature-extended.channel.min-time.label = Today Min Timestamp +channel-group-type.netatmo.temperature-extended.channel.min-time.description = Moment when temperature was measured at its minimum today. +channel-group-type.netatmo.temperature-extended.channel.trend.label = Temperature Trend +channel-group-type.netatmo.temperature-outside.label = Temperature +channel-group-type.netatmo.temperature-outside.channel.max-time.label = Today Max Timestamp +channel-group-type.netatmo.temperature-outside.channel.max-time.description = Moment when temperature was measured at its maximum today. +channel-group-type.netatmo.temperature-outside.channel.min-time.label = Today Min Timestamp +channel-group-type.netatmo.temperature-outside.channel.min-time.description = Moment when temperature was measured at its minimum today. +channel-group-type.netatmo.temperature-outside.channel.trend.label = Temperature Trend +channel-group-type.netatmo.temperature.label = Temperature +channel-group-type.netatmo.temperature.channel.max-time.label = Today Max Timestamp +channel-group-type.netatmo.temperature.channel.max-time.description = Moment when temperature was measured at its maximum today. +channel-group-type.netatmo.temperature.channel.min-time.label = Today Min Timestamp +channel-group-type.netatmo.temperature.channel.min-time.description = Moment when temperature was measured at its minimum today. +channel-group-type.netatmo.th-properties.label = Thermostat +channel-group-type.netatmo.timestamp-extended.label = Timestamp +channel-group-type.netatmo.timestamp-extended.channel.last-seen.label = Last Seen +channel-group-type.netatmo.timestamp-extended.channel.last-seen.description = Last time the module reported its presence. +channel-group-type.netatmo.timestamp-extended.channel.measures.label = Measures Timestamp +channel-group-type.netatmo.timestamp-extended.channel.measures.description = Moment of the last measures update. +channel-group-type.netatmo.timestamp.label = Timestamp +channel-group-type.netatmo.timestamp.channel.last-seen.label = Last Seen +channel-group-type.netatmo.timestamp.channel.last-seen.description = Last time the module reported its presence. +channel-group-type.netatmo.wind.label = Wind +channel-group-type.netatmo.wind.channel.max-strength-date.label = Date Max Wind Strength +channel-group-type.netatmo.wind.channel.max-strength-date.description = Moment when max wind strength was recorded. + +# channel types + +channel-type.netatmo.absolute-pressure.label = Absolute Pressure +channel-type.netatmo.absolute-pressure.description = Pressure measured relative to a full vacuum. +channel-type.netatmo.alim-status.label = Alim State +channel-type.netatmo.alim-status.description = State of the power connector +channel-type.netatmo.alim-status.state.option.ALIM_INCORRECT_POWER = Incorrect power adapter +channel-type.netatmo.alim-status.state.option.ALIM_CORRECT_POWER = Correct power adapter +channel-type.netatmo.anticipating-heating.label = Anticipated Heating +channel-type.netatmo.anticipating-heating.description = Anticipates next scheduled setpoint. +channel-type.netatmo.at-home.label = At Home +channel-type.netatmo.at-home.description = Indicates if this person is known to be at home or not. +channel-type.netatmo.avatar-picture-url.label = Avatar Picture URL +channel-type.netatmo.avatar-picture-url.description = URL for the avatar of this person. +channel-type.netatmo.avatar-picture.label = Avatar Picture +channel-type.netatmo.avatar-picture.description = Avatar of this person. +channel-type.netatmo.battery-status.label = Battery Status +channel-type.netatmo.battery-status.description = Description of the battery status. +channel-type.netatmo.camera-event.label = Camera Event +channel-type.netatmo.camera-id.label = Camera ID +channel-type.netatmo.camera-id.description = ID of the camera that triggered the event. +channel-type.netatmo.co2.label = CO2 +channel-type.netatmo.co2.description = Air Quality indicator. +channel-type.netatmo.dewpoint-depression.label = Dewpoint Depression +channel-type.netatmo.dewpoint-depression.description = Difference between the temperature and the dewpoint. +channel-type.netatmo.dewpoint.label = Dewpoint +channel-type.netatmo.dewpoint.description = Temperature to which air must be cooled to become saturated with water vapor. +channel-type.netatmo.energy-mode.label = House Mode +channel-type.netatmo.energy-mode.description = Chosen mode for the house (schedule, away, frost guard, manual). +channel-type.netatmo.energy-mode.state.option.SCHEDULE = Following a weekly schedule +channel-type.netatmo.energy-mode.state.option.AWAY = Applying the -away- temperature as defined by the user +channel-type.netatmo.energy-mode.state.option.FROST_GUARD = Frost-guard +channel-type.netatmo.energy-mode.state.option.MANUAL = Applying a manually set temperature setpoint +channel-type.netatmo.event-picture-url.label = Event Snapshot URL +channel-type.netatmo.event-picture-url.description = Url of the event snapshot. +channel-type.netatmo.event-picture.label = Event Snapshot +channel-type.netatmo.event-picture.description = Capture image of the event. +channel-type.netatmo.event-subtype.label = Event Sub Type +channel-type.netatmo.event-subtype.description = Details of the event. +channel-type.netatmo.event-subtype.state.option.SD_CARD_MISSING = Missing SD Card +channel-type.netatmo.event-subtype.state.option.SD_CARD_INSERTED = SD Card inserted +channel-type.netatmo.event-subtype.state.option.SD_CARD_FORMATTED = SD Card formated +channel-type.netatmo.event-subtype.state.option.SD_CARD_WORKING = Working SD Card +channel-type.netatmo.event-subtype.state.option.SD_CARD_DEFECTIVE = Defective SD Card +channel-type.netatmo.event-subtype.state.option.SD_CARD_INCOMPATIBLE_SPEED = Incompatible SD Card speed +channel-type.netatmo.event-subtype.state.option.SD_CARD_INSUFFICIENT_SPACE = Insufficient SD Card space +channel-type.netatmo.event-subtype.state.option.ALIM_INCORRECT_POWER = Incorrect power adapter +channel-type.netatmo.event-subtype.state.option.ALIM_CORRECT_POWER = Correct power adapter +channel-type.netatmo.event-subtype.state.option.PERSON_ARRIVAL = Person arrived +channel-type.netatmo.event-subtype.state.option.PERSON_DEPARTURE = Person has left +channel-type.netatmo.event-subtype.state.option.PERSON_SEEN = Person has been seen +channel-type.netatmo.event-subtype.state.option.MOVEMENT_HUMAN = Human seen +channel-type.netatmo.event-subtype.state.option.MOVEMENT_VEHICLE = Car seen +channel-type.netatmo.event-subtype.state.option.MOVEMENT_ANIMAL = Animal seen +channel-type.netatmo.event-type.label = Event Type +channel-type.netatmo.event-type.description = Description of the event. +channel-type.netatmo.event-type.state.option.PERSON = Face detected +channel-type.netatmo.event-type.state.option.PERSON_AWAY = Person has left home +channel-type.netatmo.event-type.state.option.PERSON_HOME = Person is at home +channel-type.netatmo.event-type.state.option.OUTDOOR = Motion detected by Presence +channel-type.netatmo.event-type.state.option.MOVEMENT = Motion detected +channel-type.netatmo.event-type.state.option.HUMAN = Human seen +channel-type.netatmo.event-type.state.option.ANIMAL = Animal seen +channel-type.netatmo.event-type.state.option.NEW_MODULE = New Module has been paired +channel-type.netatmo.event-type.state.option.MODULE_CONNECT = Module is connected with the Indoor Camera +channel-type.netatmo.event-type.state.option.MODULE_DISCONNECT = Module lost its connection with the Indoor Camera +channel-type.netatmo.event-type.state.option.MODULE_LOW_BATTERY = Module's battery is low +channel-type.netatmo.event-type.state.option.MODULE_END_UPDATE = Module's firmware update is over +channel-type.netatmo.event-type.state.option.CONNECTION = Camera connected to Netatmo +channel-type.netatmo.event-type.state.option.DISCONNECTION = Camera disconnected from Netatmo +channel-type.netatmo.event-type.state.option.ON = Monitoring activated +channel-type.netatmo.event-type.state.option.OFF = Monitoring stopped +channel-type.netatmo.event-type.state.option.BOOT = Camera booting +channel-type.netatmo.event-type.state.option.SD = SD card status changed +channel-type.netatmo.event-type.state.option.ALIM = Power status changed +channel-type.netatmo.floodlight-mode.label = Floodlight +channel-type.netatmo.floodlight-mode.description = State of the floodlight (On/Off/Auto) +channel-type.netatmo.floodlight-mode.state.option.ON = On +channel-type.netatmo.floodlight-mode.state.option.OFF = Off +channel-type.netatmo.floodlight-mode.state.option.AUTO = Auto +channel-type.netatmo.gust-angle.label = Gust Angle +channel-type.netatmo.gust-angle.description = Direction of the last 5 minutes highest gust wind +channel-type.netatmo.gust-strength.label = Gust Strength +channel-type.netatmo.gust-strength.description = Speed of the last 5 minutes highest gust wind +channel-type.netatmo.health-index.label = Health Index +channel-type.netatmo.health-index.description = Health index (healthy, fine, fair, poor, unhealthy). +channel-type.netatmo.health-index.state.option.0 = Healthy +channel-type.netatmo.health-index.state.option.1 = Fine +channel-type.netatmo.health-index.state.option.2 = Fair +channel-type.netatmo.health-index.state.option.3 = Poor +channel-type.netatmo.health-index.state.option.4 = Unhealthy +channel-type.netatmo.heat-index.label = Heat Index +channel-type.netatmo.heat-index.description = Apparent computed temperature (based on temperature and humidity). +channel-type.netatmo.heating-status.label = Heating Status +channel-type.netatmo.heating-status.description = Is the furnace currently heating? +channel-type.netatmo.home-event.label = Home Event +channel-type.netatmo.humidex-scale.label = Humidex Appreciation +channel-type.netatmo.humidex-scale.description = Appreciation of the Humidex. +channel-type.netatmo.humidex-scale.state.option.0 = Comfortable +channel-type.netatmo.humidex-scale.state.option.1 = Some discomfort +channel-type.netatmo.humidex-scale.state.option.2 = Great discomfort +channel-type.netatmo.humidex-scale.state.option.3 = Dangerous +channel-type.netatmo.humidex-scale.state.option.4 = Very dangerous +channel-type.netatmo.humidex.label = Humidex +channel-type.netatmo.humidex.description = Computed Humidex: felt temperature. +channel-type.netatmo.live-picture-url.label = Live Snapshot URL +channel-type.netatmo.live-picture-url.description = URL of the live snapshot for this camera (need scope access_camera). +channel-type.netatmo.live-picture.label = Live Snapshot +channel-type.netatmo.live-picture.description = Camera Live Snapshot. +channel-type.netatmo.live-stream-url.label = Live Stream URL +channel-type.netatmo.live-stream-url.description = URL of the live stream for this camera. +channel-type.netatmo.location.label = Location +channel-type.netatmo.location.description = Location of the device +channel-type.netatmo.max-temp.label = Max Temp +channel-type.netatmo.max-temp.description = Maximum Temperature on current day. +channel-type.netatmo.max-wind-strength.label = Max Wind Strength +channel-type.netatmo.max-wind-strength.description = Maximum wind strength recorded +channel-type.netatmo.message.label = Message +channel-type.netatmo.message.description = Message sent by Netatmo corresponding to given event. +channel-type.netatmo.min-temp.label = Min Temp +channel-type.netatmo.min-temp.description = Minimum Temperature on current day +channel-type.netatmo.monitoring-status.label = Monitoring +channel-type.netatmo.monitoring-status.description = Monitoring state of the camera +channel-type.netatmo.noise.label = Noise +channel-type.netatmo.noise.description = Current Noise Level. +channel-type.netatmo.person-count.label = Person Count +channel-type.netatmo.person-count.description = Total number of persons that are at home. +channel-type.netatmo.person-id.label = Person ID +channel-type.netatmo.planning.label = Planning +channel-type.netatmo.planning.description = Planning currently applied when following weekly schedule. +channel-type.netatmo.rain-intensity.label = Rain Intensity +channel-type.netatmo.rain-intensity.description = Current precipitation intensity. +channel-type.netatmo.rain-quantity.label = Rain Quantity +channel-type.netatmo.rain-quantity.description = Quantity of water over the period. +channel-type.netatmo.room-heating-percent.label = Heating Power +channel-type.netatmo.room-heating-percent.description = Percentage of heating power. +channel-type.netatmo.rssi.label = Signal +channel-type.netatmo.rssi.description = Signal strength indicator. +channel-type.netatmo.sd-card-status.label = SD Card State +channel-type.netatmo.sd-card-status.description = State of the SD card +channel-type.netatmo.sd-card-status.state.option.SD_CARD_MISSING = Missing SD Card +channel-type.netatmo.sd-card-status.state.option.SD_CARD_INSERTED = SD Card inserted +channel-type.netatmo.sd-card-status.state.option.SD_CARD_FORMATTED = SD Card formated +channel-type.netatmo.sd-card-status.state.option.SD_CARD_WORKING = Working SD Card +channel-type.netatmo.sd-card-status.state.option.SD_CARD_DEFECTIVE = Defective SD Card +channel-type.netatmo.sd-card-status.state.option.SD_CARD_INCOMPATIBLE_SPEED = Incompatible SD Card speed +channel-type.netatmo.sd-card-status.state.option.SD_CARD_INSUFFICIENT_SPACE = Insufficient SD Card space +channel-type.netatmo.setpoint-duration.label = Setpoint Duration +channel-type.netatmo.setpoint-duration.description = Default duration of manual setpoint changes. +channel-type.netatmo.setpoint.label = Setpoint +channel-type.netatmo.setpoint.description = Thermostat temperature setpoint. +channel-type.netatmo.th-mode.label = Thermostat Mode +channel-type.netatmo.th-mode.description = Chosen thermostat mode (home, frost guard, manual, max). +channel-type.netatmo.th-mode.state.option.HOME = Home +channel-type.netatmo.th-mode.state.option.FROST_GUARD = Frost Guard +channel-type.netatmo.th-mode.state.option.MANUAL = Manual +channel-type.netatmo.th-mode.state.option.MAX = Max +channel-type.netatmo.timestamp-advanced.label = Timestamp +channel-type.netatmo.timestamp-advanced.description = Moment when data was measured. +channel-type.netatmo.timestamp.label = Timestamp +channel-type.netatmo.timestamp.description = Moment when data was measured. +channel-type.netatmo.trend.label = Trend +channel-type.netatmo.trend.description = Evolution of the measure over time. +channel-type.netatmo.trend.state.option.UP = Up +channel-type.netatmo.trend.state.option.STABLE = Stable +channel-type.netatmo.trend.state.option.DOWN = Down +channel-type.netatmo.unknown-person-count.label = Unknown Persons Count +channel-type.netatmo.unknown-person-count.description = Total number of unknown persons that are at home. +channel-type.netatmo.unknown-person-picture.label = Unknown Person Snapshot +channel-type.netatmo.unknown-person-picture.description = Snapshot of unknown person that is at home. +channel-type.netatmo.video-status.label = Video Status +channel-type.netatmo.video-status.description = Status of the video (recording, deleted or available). +channel-type.netatmo.video-status.state.option.RECORDING = Recording +channel-type.netatmo.video-status.state.option.DELETED = Deleted +channel-type.netatmo.video-status.state.option.AVAILABLE = Available +channel-type.netatmo.video-url.label = Video URL +channel-type.netatmo.video-url.description = URL of the event recording. +channel-type.netatmo.window-open.label = Window Status +channel-type.netatmo.window-open.description = Windows of the room are opened. + +# channel types config + +channel-type.config.netatmo.live-stream-url.quality.label = Quality Level +channel-type.config.netatmo.live-stream-url.quality.description = Defines quality level of the feed (the higher the more bandwidth) +channel-type.config.netatmo.live-stream-url.quality.option.low = Low Quality +channel-type.config.netatmo.live-stream-url.quality.option.poor = Poor Quality +channel-type.config.netatmo.live-stream-url.quality.option.high = High Quality + +# thing types + +thing-type.netatmo.account.label = Netatmo Account +thing-type.netatmo.account.description = This bridge represents an account, gateway to Netatmo API. +thing-type.netatmo.doorbell.label = Smart Video Doorbell +thing-type.netatmo.doorbell.description = The Netatmo Smart Video Doorbell device. +thing-type.netatmo.home-coach.label = Healthy Home Coach +thing-type.netatmo.home-coach.description = Healthy home coach reporting health-index, temperature, humidity, pressure, air quality and sound level. +thing-type.netatmo.home.label = Home +thing-type.netatmo.home.description = A home hosting Security or Energy devices and modules. +thing-type.netatmo.indoor.label = Additional Module +thing-type.netatmo.indoor.description = Additional indoor module reporting temperature, humidity and CO2 level. +thing-type.netatmo.outdoor.label = Outdoor Module +thing-type.netatmo.outdoor.description = Outdoor module reporting temperature and humidity. +thing-type.netatmo.person.label = Person +thing-type.netatmo.person.description = A person known by your Netatmo system. +thing-type.netatmo.plug.label = Relay Plug +thing-type.netatmo.plug.description = The relay connected to the boiler controlling a Thermostat and zero or more valves. +thing-type.netatmo.presence.label = Smart Outdoor Camera +thing-type.netatmo.presence.description = The Netatmo Smart Outdoor Camera (Presence) camera with or without siren. +thing-type.netatmo.rain.label = Rain Gauge +thing-type.netatmo.rain.description = Rain Gauge measuring precipitation. +thing-type.netatmo.room.label = Room +thing-type.netatmo.room.description = A room in your house. +thing-type.netatmo.siren.label = Siren Module +thing-type.netatmo.siren.description = The Netatmo Smart Indoor Siren. +thing-type.netatmo.thermostat.label = Thermostat Module +thing-type.netatmo.thermostat.description = The Thermostat device placed in a given room. +thing-type.netatmo.valve.label = Radiator valve +thing-type.netatmo.valve.description = A valve controlling a radiator. +thing-type.netatmo.weather-station.label = Weather Station +thing-type.netatmo.weather-station.description = Main indoor module reporting temperature, humidity, pressure, air quality and sound level. +thing-type.netatmo.welcome.label = Smart Indoor Camera +thing-type.netatmo.welcome.description = The Netatmo Smart Indoor Camera (Welcome). +thing-type.netatmo.wind.label = Wind Gauge Module +thing-type.netatmo.wind.description = Wind sensor reporting wind angle and strength. + +# error messages + +conf-error-no-client-id = Cannot connect to Netatmo bridge as no client id is available in the configuration +conf-error-no-client-secret = Cannot connect to Netatmo bridge as no client secret is available in the configuration +conf-error-no-username = Cannot connect to Netatmo bridge as no username is available in the configuration +conf-error-no-password = Cannot connect to Netatmo bridge as no password is available in the configuration +status-bridge-offline = Bridge is not connected to Netatmo API +device-not-connected = Thing is not reachable +data-over-limit = Data seems quite old +request-time-out = Request timed out - will attempt reconnection later + +# actions + +actionInputSetpointLabel = Setpoint +actionInputSetpointDesc = The temperature setpoint +actionInputEndtimeLabel = Endtime +actionInputEndtimeDesc = Time the setpoint should end +actionInputModeLabel = Mode +actionInputModeDesc = The mode to set: MANUAL, SCHEDULE or FG (Frost-Guard) +actionLabel = send a set room thermpoint command +actionDesc = Send set room thermpoint command with endtime. +reconnectApiLabel = Reconnect API +reconnectApiDesc = Reopens the Netatmo API session. diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_de.properties b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_de.properties deleted file mode 100644 index 8c4289a458575..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_de.properties +++ /dev/null @@ -1,334 +0,0 @@ -# binding -binding.netatmo.name = Netatmo Binding -binding.netatmo.description = Dieses Binding integriert die smarte Wetterstation, die Zusatzmodule wie Regenmesser und Windmesser, als auch das smarte Thermostat und den Healthy Home Coach. - -# bridge types -thing-type.netatmo.netatmoapi.label = Netatmo API -thing-type.netatmo.netatmoapi.description = Netatmo API - -# bridge types config -thing-type.config.netatmo.bridge.clientId.label = Client id -thing-type.config.netatmo.bridge.clientId.description = Client id der APP, die unter https://dev.netatmo.com/myaccount/createanapp erstellt wurde. - -thing-type.config.netatmo.bridge.clientSecret.label = Client secret -thing-type.config.netatmo.bridge.clientSecret.description = Client secret der APP, die unter https://dev.netatmo.com/myaccount/createanapp erstellt wurde. - -thing-type.config.netatmo.bridge.username.label = Benutzer -thing-type.config.netatmo.bridge.username.description = Benutzer zur Authentifizierung an der Netatmo API. - -thing-type.config.netatmo.bridge.password.label = Passwort -thing-type.config.netatmo.bridge.password.description = Passwort zur Authentifizierung an der Netatmo API. - -thing-type.config.netatmo.bridge.readStation.label = Wetterstation API -thing-type.config.netatmo.bridge.readStation.description = Aktiviert den Zugriff auf die Wetterstation API (lesend). - -thing-type.config.netatmo.bridge.readHealthyHomeCoach.label = Healthy Home Coach API -thing-type.config.netatmo.bridge.readHealthyHomeCoach.description = Aktiviert den Zugriff auf die Healthy Home Coach API (lesend). - -thing-type.config.netatmo.bridge.readThermostat.label = Thermostat API -thing-type.config.netatmo.bridge.readThermostat.description = Aktiviert den Zugriff auf die Thermostat API (lesend und schreibend). - -#thing-type.config.netatmo.bridge.readWelcome.label = Access Welcome camera -#thing-type.config.netatmo.bridge.readWelcome.description = Read and Access Welcome camera's data. - -#thing-type.config.netatmo.bridge.readPresence.label = Access Presence camera -#thing-type.config.netatmo.bridge.readPresence.description = Read and Access Presence camera's data. - -#thing-type.config.netatmo.bridge.webHookUrl.label = Webhook address -#thing-type.config.netatmo.bridge.webHookUrl.description = Protocol, public IP and port to access OH2 server from Internet. - -#thing-type.config.netatmo.bridge.reconnectInterval.label = Reconnect Interval -#thing-type.config.netatmo.bridge.reconnectInterval.description = The reconnection interval to Netatmo API (in s). - -# thing types -thing-type.netatmo.NAMain.label = Haupt-Indoor-Modul -thing-type.netatmo.NAMain.description = Das Haupt-Indoor-Modul liefert Daten wie z.B. Temperatur, Luftdruck, Luftfeuchtigkeit, CO2-Gehalt und Lautstärke. - -thing-type.netatmo.NAModule1.label = Outdoor-Modul -thing-type.netatmo.NAModule1.description = Das Outdoor-Modul liefert Daten wie z.B. Temperatur und Luftdruck. - -thing-type.netatmo.NAModule2.label = Windmesser -thing-type.netatmo.NAModule2.description = Der Windmesser liefert Daten wie z.B. Windrichtung und Windstärke. - -thing-type.netatmo.NAModule3.label = Regenmesser -thing-type.netatmo.NAModule3.description = Der Regenmesser liefert Daten wie z.B. Echtzeit-Messungen zur Intensität des Regens. - -thing-type.netatmo.NAModule4.label = Zusatz-Indoor-Modul -thing-type.netatmo.NAModule4.description = Das Zusatz-Indoor-Modul liefert Daten wie z.B. Temperatur, Luftdruck, Luftfeuchtigkeit und CO2-Gehalt. - -thing-type.netatmo.NAPlug.label = Thermostat Relais -thing-type.netatmo.NAPlug.description = Das Thermostat Relais liefert Daten des Heizkessels. - -thing-type.netatmo.NATherm1.label = Heizkörperthermostat -thing-type.netatmo.NATherm1.description = Das Heizkörperthermostat dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur. - -thing-type.netatmo.NHC.label = Healthy Home Coach -thing-type.netatmo.NHC.description = Der Healthy Home Coach liefert Daten wie z.B. Temperatur, Luftdruck, Luftfeuchtigkeit, CO2-Gehalt und Lautstärke. - -#thing-type.netatmo.NAWelcomeHome.label = Welcome Home -#thing-type.netatmo.NAWelcomeHome.description = This represents a home hosting a Welcome Camera. - -#thing-type.netatmo.NACamera.label = Welcome Camera -#thing-type.netatmo.NACamera.description = This represents a welcome camera at home. - -#thing-type.netatmo.NAWelcomePerson.label = Welcome Person -#thing-type.netatmo.NAWelcomePerson.description = This represents a person at home. - -# thing types config -thing-type.config.netatmo.station.id.label = MAC-Adresse Modul -thing-type.config.netatmo.station.id.description = MAC-Adresse des Haupt-Indoor-Moduls. - -thing-type.config.netatmo.module.id.label = MAC-Adresse Zusatzmodule -thing-type.config.netatmo.module.id.description = MAC-Adresse des Zusatzmoduls. - -thing-type.config.netatmo.module.parentId.label = MAC-Adresse Haupt-Indoor-Modul -thing-type.config.netatmo.module.parentId.description = MAC-Adresse des Haupt-Indoor-Moduls. - -#thing-type.config.netatmo.plug.id.label = Equipment ID -#thing-type.config.netatmo.plug.id.description = Id of the Device (mac address). - -#thing-type.config.netatmo.natherm1.id.label = Module ID -#thing-type.config.netatmo.natherm1.id.description = Id of the Module. - -#thing-type.config.netatmo.natherm1.parentId.label = Device ID -#thing-type.config.netatmo.natherm1.parentId.description = Id of the main device. - -#thing-type.config.netatmo.natherm1.setpointDefaultDuration.label = Setpoint duration -#thing-type.config.netatmo.natherm1.setpointDefaultDuration.description = Default duration of thermostat change when force to max or manual. - -#thing-type.config.netatmo.welcomehome.id.label = Home ID -#thing-type.config.netatmo.welcomehome.id.description = UUID of the home - -#thing-type.config.netatmo.welcomehome.refreshInterval.label = Refresh Interval -#thing-type.config.netatmo.welcomehome.refreshInterval.description = The refresh interval to poll Netatmo API (in ms). - -#thing-type.config.netatmo.camera.id.label = Camera ID -#thing-type.config.netatmo.camera.id.description = Camera MAC Address - -#thing-type.config.netatmo.camera.parentId.label = Home ID -#thing-type.config.netatmo.camera.parentId.description = UUID of the home hosting the camera - -#thing-type.config.netatmo.nawelcomeperson.id.label = Person ID -#thing-type.config.netatmo.nawelcomeperson.id.description = UUID of the Person - -#thing-type.config.netatmo.nawelcomeperson.parentId.label = Home ID -#thing-type.config.netatmo.nawelcomeperson.parentId.description = UUID of the home - -# channel types -channel-type.netatmo.co2.label = CO2-Gehalt -channel-type.netatmo.co2.description = Zeigt den aktuellen CO2-Gehalt an. - -channel-type.netatmo.temperature.label = Temperatur -channel-type.netatmo.temperature.description = Zeigt die aktuelle Temperatur an. - -channel-type.netatmo.minTemp.label = Min. Temperatur -channel-type.netatmo.minTemp.description = Zeigt die minimale Temperatur des aktuellen Tages an. - -channel-type.netatmo.maxTemp.label = Max. Temperatur -channel-type.netatmo.maxTemp.description = Zeigt die maximale Temperatur des aktuellen Tages an. - -channel-type.netatmo.dateMinTemp.label = Zeitpunkt Min. Temperatur -channel-type.netatmo.dateMinTemp.description = Zeigt den Zeitpunkt an, an dem die minimale Temperatur gemessen wurde. - -channel-type.netatmo.dateMaxTemp.label = Zeitpunkt Max. Temperatur -channel-type.netatmo.dateMaxTemp.description = Zeigt den Zeitpunkt an, an dem die maximale Temperatur gemessen wurde. - -channel-type.netatmo.temperatureTrend.label = Temperaturtrend -channel-type.netatmo.temperatureTrend.description = Zeigt den Temperaturtrend (z.B. "Steigend", "Stabil" oder "Fallend") an. -channel-type.netatmo.temperatureTrend.state.option.up = Steigend -channel-type.netatmo.temperatureTrend.state.option.stable = Stabil -channel-type.netatmo.temperatureTrend.state.option.down = Fallend - -channel-type.netatmo.humidity.label = Luftfeuchtigkeit -channel-type.netatmo.humidity.description = Zeigt die aktuelle Luftfeuchtigkeit an. - -channel-type.netatmo.humidex.label = Gef. Temperatur (Humidex) -channel-type.netatmo.humidex.description = Zeigt die gefühlte Temperatur an. Basierend auf dem Humidex. - -channel-type.netatmo.heatIndex.label = Gef. Temperatur (Hitzeindex) -channel-type.netatmo.heatIndex.description = Zeigt die gefühlte Temperatur an. Basierend auf dem Hitzeindex. - -channel-type.netatmo.dewPoint.label = Taupunkt -channel-type.netatmo.dewPoint.description = Zeigt den Taupunkt an. - -channel-type.netatmo.dewPointDepression.label = Taupunktsdifferenz -channel-type.netatmo.dewPointDepression.description = Zeigt die Taupunktsdifferenz an. - -channel-type.netatmo.noise.label = Sonometer -channel-type.netatmo.noise.description = Zeigt die aktuelle Lautstärke an. - -channel-type.netatmo.pressure.label = Luftdruck -channel-type.netatmo.pressure.description = Zeigt den aktuellen Luftdruck an. - -channel-type.netatmo.pressureTrend.label = Luftdrucktrend -channel-type.netatmo.pressureTrend.description = Zeigt den Luftdrucktrend an (z.B. "Steigend", "Stabil" oder "Fallend"). -channel-type.netatmo.pressureTrend.state.option.up = Steigend -channel-type.netatmo.pressureTrend.state.option.stable = Stabil -channel-type.netatmo.pressureTrend.state.option.down = Fallend - -channel-type.netatmo.absolutePressure.label = Absoluter Luftdruck -channel-type.netatmo.absolutePressure.description = Zeigt den absoluten Luftdruck an. - -channel-type.netatmo.lastStatusStore.label = Letzte Übertragung -channel-type.netatmo.lastStatusStore.description = Zeigt den Zeitpunkt der letzten Datenübertragung an. - -channel-type.netatmo.timeUtc.label = Letzte Messung -channel-type.netatmo.timeUtc.description = Zeigt den Zeitpunkt der letzten Datenmessung an. - -channel-type.netatmo.lastMessage.label = Letzte Meldung -channel-type.netatmo.lastMessage.description = Zeigt den Zeitpunkt der letzten Meldung an. - -channel-type.netatmo.location.label = Standort -channel-type.netatmo.location.description = Zeigt die geographischen Koordinaten (Breitengrad, Längengrad, Höhe über dem Meerespiegel) (in °N, °W, m) an. - -channel-type.netatmo.rain.label = Niederschlag -channel-type.netatmo.rain.description = Zeigt den aktuellen Niederschlag an. - -channel-type.netatmo.rain1.label = Kumulierter Niederschlag (1h) -channel-type.netatmo.rain1.description = Zeigt den kumulierten Niederschlag der letzten Stunde an. - -channel-type.netatmo.rain24.label = Kumulierter Niederschlag (24h) -channel-type.netatmo.rain24.description = Zeigt den kumulierten Niederschlag der letzten 24h an. - -channel-type.netatmo.WindAngle.label = Windrichtung -channel-type.netatmo.WindAngle.description = Zeigt die aktuelle Windrichtung an. - -channel-type.netatmo.WindStrength.label = Windgeschwindigkeit -channel-type.netatmo.WindStrength.description = Zeigt die aktuelle Windgeschwindigkeit an. - -channel-type.netatmo.GustAngle.label = Böen Richtung -channel-type.netatmo.GustAngle.description = Zeigt die aktuelle Böen Richtung an. - -channel-type.netatmo.GustStrength.label = Böen Geschwindigkeit -channel-type.netatmo.GustStrength.description = Zeigt die aktuelle Böen Geschwindigkeit an. - -channel-type.netatmo.healthindex.label = Health Index -channel-type.netatmo.healthindex.description = Zeigt den Health Index (z.B. "Gesund", "Gut", "Angemessen", "Schlecht" oder "Ungesund") an. -channel-type.netatmo.healthindex.state.option.healthy = Gesund -channel-type.netatmo.healthindex.state.option.fine = Gut -channel-type.netatmo.healthindex.state.option.fair = Angemessen -channel-type.netatmo.healthindex.state.option.poor = Schlecht -channel-type.netatmo.healthindex.state.option.unhealthy = Ungesund - -#channel-type.netatmo.connectedBoiler.label = Plug Connected Boiler -#channel-type.netatmo.connectedBoiler.description = - -#channel-type.netatmo.lastPlugSeen.label = Last Plug Seen -#channel-type.netatmo.lastPlugSeen.description = Last Plug Seen - -#channel-type.netatmo.lastBilan.label = Available Bilan -#channel-type.netatmo.lastBilan.description = Month of the last available thermostat bilan - -#channel-type.netatmo.setpointTemp.label = Setpoint -#channel-type.netatmo.setpointTemp.description = Thermostat temperature setpoint - -#channel-type.netatmo.setpointMode.label = Setpoint Mode -#channel-type.netatmo.setpointMode.description = Chosen setpoint_mode (program, away, hg, manual, off, max) -#channel-type.netatmo.setpointMode.state.option.program = Following a weekly schedule -#channel-type.netatmo.setpointMode.state.option.away = Applying the -away- temperature as defined by the user -#channel-type.netatmo.setpointMode.state.option.hg = Frost-guard -#channel-type.netatmo.setpointMode.state.option.manual = Applying a manually set temperature setpoint -#channel-type.netatmo.setpointMode.state.option.off = Currently off -#channel-type.netatmo.setpointMode.state.option.max = Heating continuously - -#channel-type.netatmo.ThermRelayCmd.label = Heating status -#channel-type.netatmo.ThermRelayCmd.description = Indicates whether the furnace is heating or not - -#channel-type.netatmo.ThermOrientation.label = Orientation -#channel-type.netatmo.ThermOrientation.description = Physical orientation of the thermostat module - -#channel-type.netatmo.setpointEndTime.label = Setpoint end -#channel-type.netatmo.setpointEndTime.description = Thermostat goes back to schedule after that timestamp. - -#channel-type.netatmo.homecity.label = City -#channel-type.netatmo.homecity.description = City of the home - -#channel-type.netatmo.homecountry.label = Country -#channel-type.netatmo.homecountry.description = Country of the home - -#channel-type.netatmo.hometimezone.label = Timezone -#channel-type.netatmo.hometimezone.description = Timezone of the home - -#channel-type.netatmo.homepersoncount.label = Person counter -#channel-type.netatmo.homepersoncount.description = Total number of Persons that are at home - -#channel-type.netatmo.homeunknowncount.label = Unknown Person counter -#channel-type.netatmo.homeunknowncount.description = Count how many Unknown Persons are at home - -#channel-type.netatmo.type.label = Type -#channel-type.netatmo.type.description = Type of event - -#channel-type.netatmo.time.label = Time -#channel-type.netatmo.time.description = Time of occurrence of event - -#channel-type.netatmo.camera_id.label = Camera ID -#channel-type.netatmo.camera_id.description = Camera that detected the event - -#channel-type.netatmo.person_id.label = Person ID -#channel-type.netatmo.person_id.description = Id of the person the event is about (if any) - -#channel-type.netatmo.snapshot_url.label = Snapshot URL -#channel-type.netatmo.snapshot_url.description = Url of the event snapshot - -#channel-type.netatmo.snapshot.label = Event Snapshot -#channel-type.netatmo.snapshot.description = Event Snapshot - -#channel-type.netatmo.video_url.label = Video URL -#channel-type.netatmo.video_url.description = URL of the event video - -#channel-type.netatmo.video_status.label = Video status -#channel-type.netatmo.video_status.description = Status of the video (recording, deleted or available) - -#channel-type.netatmo.is_arrival.label = Is arrival -#channel-type.netatmo.is_arrival.description = If person was considered "away" before being seen during this event - -#channel-type.netatmo.message.label = Message -#channel-type.netatmo.message.description = Message sent by Netatmo corresponding to given event - -#channel-type.netatmo.sub_type.label = Sub Type -#channel-type.netatmo.sub_type.description = Sub-type of SD and Alim events - -#channel-type.netatmo.status.label = State -#channel-type.netatmo.status.description = State of the camera - -#channel-type.netatmo.sd_status.label = SD State -#channel-type.netatmo.sd_status.description = State of the SD card - -#channel-type.netatmo.alim_status.label = Alim State -#channel-type.netatmo.alim_status.description = State of the power connector - -#channel-type.netatmo.is_locale.label = Is local -#channel-type.netatmo.is_locale.description = Indicates whether the camera is on the same network than the openHab Netatmo Binding - -#channel-type.netatmo.live_picture_url.label = Live snapshot URL -#channel-type.netatmo.live_picture_url.description = Url of the live snapshot for this camera - -#channel-type.netatmo.live_picture.label = Live Snapshot -#channel-type.netatmo.live_picture.description = Camera Live Snapshot - -#channel-type.netatmo.live_stream_url.label = Live stream URL -#channel-type.netatmo.live_stream_url.description = Url of the live stream for this camera - -#channel-type.netatmo.last_seen.label = Last seen -#channel-type.netatmo.last_seen.description = Time when this person was last seen - -#channel-type.netatmo.person_athome.label = At home -#channel-type.netatmo.person_athome.description = Indicates if this person is known to be at home or not - -#channel-type.netatmo.person_eventmsg.label = Last Event message -#channel-type.netatmo.person_eventmsg.description = Last Event message from this person - -#channel-type.netatmo.person_eventtime.label = Last Event time -#channel-type.netatmo.person_eventtime.description = Last Event message time for this person - -#channel-type.netatmo.person_avatar_url.label = Avatar URL -#channel-type.netatmo.person_avatar_url.description = URL for the avatar of this person - -#channel-type.netatmo.person_avatar.label = Avatar -#channel-type.netatmo.person_avatar.description = Avatar of this person - -#channel-type.netatmo.person_event.label = Last Event Picture -#channel-type.netatmo.person_event.description = Picture of the last event for this person - -#channel-type.netatmo.person_event_url.label = Last event URL -#channel-type.netatmo.person_event_url.description = URL for the picture of the last event for this person diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_fr.properties b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_fr.properties deleted file mode 100644 index 84d40b659ffad..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo_fr.properties +++ /dev/null @@ -1,378 +0,0 @@ -# binding -binding.netatmo.name = Extension Netatmo -binding.netatmo.description = L'extension Netatmo intègre le thermostat et la station météo Netatmo ainsi que les modules additionels. - -# bridge types -thing-type.netatmo.netatmoapi.label = API Netatmo -thing-type.netatmo.netatmoapi.description = Cet élément représente la passerelle avec l'API Netatmo. - -# bridge types configuration -thing-type.config.netatmo.bridge.clientId.label = ID client -thing-type.config.netatmo.bridge.clientId.description = ID clent fourni par l'application que vous avez créée sur http://dev.netatmo.com/createapp -thing-type.config.netatmo.bridge.clientSecret.label = Secret client -thing-type.config.netatmo.bridge.clientSecret.description = Secret client fourni par l'application que vous avez créée. - -thing-type.config.netatmo.bridge.username.label = Nom d'utilisateur -thing-type.config.netatmo.bridge.username.description = Votre nom d'utilisateur (e-mail) pour l'API Netatmo. -thing-type.config.netatmo.bridge.password.label = Mot de passe -thing-type.config.netatmo.bridge.password.description = Votre mot de passe pour l'API Netatmo. - -thing-type.config.netatmo.bridge.readStation.label = Accès à la station météo -thing-type.config.netatmo.bridge.readStation.description = Accède ou non aux données de la station météo. - -thing-type.config.netatmo.bridge.readHealthyHomeCoach.label = Accès au Healthy Home Coach -thing-type.config.netatmo.bridge.readHealthyHomeCoach.description = Accède ou non aux données du Healthy Home Coach. - -thing-type.config.netatmo.bridge.readThermostat.label = Accès au thermostat -thing-type.config.netatmo.bridge.readThermostat.description = Accède ou non aux données du thermostat. - -thing-type.config.netatmo.bridge.readWelcome.label = Accès à la Camera Welcome -thing-type.config.netatmo.bridge.readWelcome.description = Accède ou non aux données de la caméra Welcome. - -thing-type.config.netatmo.bridge.readPresence.label = Accès à la Camera Presence -thing-type.config.netatmo.bridge.readPresence.description = Accède ou non aux données de la caméra Presence. - -thing-type.config.netatmo.bridge.webHookUrl.label = Adresse Webhook -thing-type.config.netatmo.bridge.webHookUrl.description = Protocole, IP publique et port pour l'accès au sereveur OH2 depuis l'Internet. - -thing-type.config.netatmo.bridge.reconnectInterval.label = Intervalle de reconnection -thing-type.config.netatmo.bridge.reconnectInterval.description = L'intervalle de reconnection à l'API Netatmo (en s). - -# thing types -thing-type.netatmo.NAMain.label = Station intérieure principale -thing-type.netatmo.NAMain.description = Module capable de mesurer la température, l'humidité, la pression, la qualité de l'air et le niveau sonore. - -thing-type.netatmo.NAModule1.label = Module Extérieur -thing-type.netatmo.NAModule1.description = Module extérieur capable de mesurer la température et l'humidité. - -thing-type.netatmo.NAModule2.label = Anémomètre -thing-type.netatmo.NAModule2.description = Module extérieur dédié au vent capable de mesurer sa direction et sa force. - -thing-type.netatmo.NAModule3.label = Capteur de Pluie -thing-type.netatmo.NAModule3.description = Module extérieur dédié à la mesure du niveau de précipitations. - -thing-type.netatmo.NAModule4.label = Module Additionel -thing-type.netatmo.NAModule4.description = Module intérieur supplémentaire capable de mesurer la température, l'humidité et le niveau de CO2. - -thing-type.netatmo.NAPlug.label = Relais Thermostat -thing-type.netatmo.NAPlug.description = Cet élément représente le relais communiquant avec le thermostat. - -thing-type.netatmo.NATherm1.label = Module Thermostat -thing-type.netatmo.NATherm1.description = Cet élément représente le module thermostat permettant de règler la température. - -thing-type.netatmo.NHC.label = Healthy Home Coach -thing-type.netatmo.NHC.description = Cet élément représente le module Healthy Home Coach capable de mesurer le niveau de confort dans la maison. - -thing-type.netatmo.NAWelcomeHome.label = Maison -thing-type.netatmo.NAWelcomeHome.description = Cet élément représente une maison hébergeant une ou plusieurs caméras Netatmo. - -thing-type.netatmo.NACamera.label = Caméra Welcome -thing-type.netatmo.NACamera.description = Cet élément représente une caméra Welcome de la maison. - -thing-type.netatmo.NAWelcomePerson.label = Personne -thing-type.netatmo.NAWelcomePerson.description = Cet élément représente une personne de la maison. - -# thing type configuration -thing-type.config.netatmo.station.id.label = ID équipement -thing-type.config.netatmo.station.id.description = ID de l'équipement (adresse MAC) - -thing-type.config.netatmo.module.id.label = ID module -thing-type.config.netatmo.module.id.description = ID du module - -thing-type.config.netatmo.module.parentId.label = ID équipement principal -thing-type.config.netatmo.module.parentId.description = ID de l'équipement principal - -thing-type.config.netatmo.plug.id.label = ID équipement -thing-type.config.netatmo.plug.id.description = ID de l'équipement (adresse MAC) - -thing-type.config.netatmo.natherm1.id.label = ID module -thing-type.config.netatmo.natherm1.id.description = ID du module - -thing-type.config.netatmo.natherm1.parentId.label = ID équipement principal -thing-type.config.netatmo.natherm1.parentId.description = ID de l'équipement principal - -thing-type.config.netatmo.natherm1.setpointDefaultDuration.label = Durée de consigne -thing-type.config.netatmo.natherm1.setpointDefaultDuration.description = Durée par défaut de consigne hors planning - -thing-type.config.netatmo.welcomehome.id.label = ID maison -thing-type.config.netatmo.welcomehome.id.description = ID de la maison - -thing-type.config.netatmo.welcomehome.refreshInterval.label = Fréquence de rafraîchissement -thing-type.config.netatmo.welcomehome.refreshInterval.description = La fréquence d'interrogation de l'API Netatmo (en ms) - -thing-type.config.netatmo.camera.id.label = ID caméra -thing-type.config.netatmo.camera.id.description = ID de la caméra (adresse MAC) - -thing-type.config.netatmo.camera.parentId.label = ID maison -thing-type.config.netatmo.camera.parentId.description = ID de la maison hébergeant la caméra - -thing-type.config.netatmo.nawelcomeperson.id.label = ID personne -thing-type.config.netatmo.nawelcomeperson.id.description = ID de la personne - -thing-type.config.netatmo.nawelcomeperson.parentId.label = ID maison -thing-type.config.netatmo.nawelcomeperson.parentId.description = ID de la maison - -# channel types -channel-type.netatmo.co2.label = CO2 -channel-type.netatmo.co2.description = Mesure de la qualité de l'air - -channel-type.netatmo.temperature.label = Température -channel-type.netatmo.temperature.description = Mesure de la tempérture - -channel-type.netatmo.temperatureTrend.label = Tendance température -channel-type.netatmo.temperatureTrend.description = Mesure de la tendance d'évolution de la tempérture -channel-type.netatmo.temperatureTrend.state.option.up = A la hausse -channel-type.netatmo.temperatureTrend.state.option.stable = Stable -channel-type.netatmo.temperatureTrend.state.option.down = A la baisse - -channel-type.netatmo.noise.label = Niveau sonore -channel-type.netatmo.noise.description = Mesure du niveau sonore de la pièce - -channel-type.netatmo.pressure.label = Pression atmosphérique -channel-type.netatmo.pressure.description = Mesure de la pression atmosphérique - -channel-type.netatmo.pressureTrend.label = Tendance pression atmosphérique -channel-type.netatmo.pressureTrend.description = Mesure de la tendance d'évolution de la pression atmosphérique sur les dernières 12 heures -channel-type.netatmo.pressureTrend.state.option.up = A la hausse -channel-type.netatmo.pressureTrend.state.option.stable = Stable -channel-type.netatmo.pressureTrend.state.option.down = A la baisse - -channel-type.netatmo.absolutePressure.label = Pression atmosphérique absolue -channel-type.netatmo.absolutePressure.description = Mesure de la pression atmosphérique absolue - -channel-type.netatmo.timeUtc.label = Horodatage des mesures -channel-type.netatmo.timeUtc.description = Date/Heure du dernier relevé de mesures - -channel-type.netatmo.humidity.label = Humidité -channel-type.netatmo.humidity.description = Mesure du niveau d'hygrométrie - -channel-type.netatmo.humidex.label = Humidex -channel-type.netatmo.humidex.description = Indice humidex calculé - -channel-type.netatmo.heatIndex.label = Indice de chaleur -channel-type.netatmo.heatIndex.description = Indice de chaleur calculé - -channel-type.netatmo.dewPoint.label = Point de rosée -channel-type.netatmo.dewPoint.description = Température du point de rosée - -channel-type.netatmo.dewPointDepression.label = Dépression du point de rosée -channel-type.netatmo.dewPointDepression.description = Ecart entre la température actuelle et le point de rosée - -channel-type.netatmo.minTemp.label = Température min -channel-type.netatmo.minTemp.description = Température minimale de la journée en cours - -channel-type.netatmo.maxTemp.label = Température max -channel-type.netatmo.maxTemp.description = Température maximale de la journée en cours - -channel-type.netatmo.dateMinTemp.label = Horodatage température min -channel-type.netatmo.dateMinTemp.description = Date/Heure de relevé de la température minimale pour la journée en cours - -channel-type.netatmo.dateMaxTemp.label = Horodatage température max -channel-type.netatmo.dateMaxTemp.description = Date/Heure de relevé de la température maximale pour la journée en cours - -channel-type.netatmo.location.label = Localisation -channel-type.netatmo.location.description = Localisation de la station Netatmo - -channel-type.netatmo.rain.label = Précipitations -channel-type.netatmo.rain.description = Volume de précipitations relevé - -channel-type.netatmo.rain1.label = Précipitations 1h -channel-type.netatmo.rain1.description = Volume de précipitations relevé durant la dernière heure - -channel-type.netatmo.rain24.label = Précipitations 24h -channel-type.netatmo.rain24.description = Volume de précipitations relevé durant la dernière journée - -channel-type.netatmo.WindAngle.label = Direction du vent -channel-type.netatmo.WindAngle.description = Direction moyenne du vent sur les 5 dernières minutes - -channel-type.netatmo.WindStrength.label = Force du vent -channel-type.netatmo.WindStrength.description = Vitesse moyenne du vent sur les 5 dernières minutes - -channel-type.netatmo.GustAngle.label = Direction rafale de vent -channel-type.netatmo.GustAngle.description = Direction moyenne des rafales de vent sur les 5 dernières minutes - -channel-type.netatmo.GustStrength.label = Force rafale de vent -channel-type.netatmo.GustStrength.description = Vitesse moyenne des rafales de vent sur les 5 dernières minutes - -channel-type.netatmo.lastStatusStore.label = Dernière demande d'état -channel-type.netatmo.lastStatusStore.description = Date/Heure de la dernière demande d'état - -channel-type.netatmo.lastMessage.label = Horodatage dernier message -channel-type.netatmo.lastMessage.description = Date/Heure du dernier message émis par le module - -channel-type.netatmo.connectedBoiler.label = Relais connecté -channel-type.netatmo.connectedBoiler.description = Indique si le relais est connecté ou non à une chaudière - -channel-type.netatmo.lastPlugSeen.label = Horodatage visibilité du relais -channel-type.netatmo.lastPlugSeen.description = Date/Heure de dernière visibilité du module relais par le thermostat - -channel-type.netatmo.lastBilan.label = Bilan Economies d'Energie -channel-type.netatmo.lastBilan.description = Mois du dernier bilan d'économies d'énergie disponible - -channel-type.netatmo.setpointTemp.label = Température de consigne -channel-type.netatmo.setpointTemp.description = Température de consigne sélectionnée sur le thermostat - -channel-type.netatmo.setpointMode.label = Mode de consigne -channel-type.netatmo.setpointMode.description = Mode de consigne choisi sur le thermostat (planning hebdo, absence, hors-gel, manuel, arrêt, en permanence) -channel-type.netatmo.setpointMode.state.option.program = Suivi du planning hebdomadaire -channel-type.netatmo.setpointMode.state.option.away = Température d'absence -channel-type.netatmo.setpointMode.state.option.hg = Hors-gel -channel-type.netatmo.setpointMode.state.option.manual = Température de consigne manuelle -channel-type.netatmo.setpointMode.state.option.off = Arrêt -channel-type.netatmo.setpointMode.state.option.max = Chauffage en permanence - -channel-type.netatmo.planning.label = Planning -channel-type.netatmo.planning.description = Planification des plages de chauffe utilisée en mode suivi du planning - -channel-type.netatmo.ThermRelayCmd.label = Etat du chauffage -channel-type.netatmo.ThermRelayCmd.description = Indique si le chauffage est en marche ou pas - -channel-type.netatmo.ThermOrientation.label = Orientation -channel-type.netatmo.ThermOrientation.description = Orientation physique du module thermostat - -channel-type.netatmo.setpointEndTime.label = Heure fin de consigne -channel-type.netatmo.setpointEndTime.description = Heure de retour au planning de chauffe - -channel-type.netatmo.healthindex.label = Indice de confort -channel-type.netatmo.healthindex.description = Indice de confort (sain, agréable, correct, mauvais, malsain) -channel-type.netatmo.healthindex.state.option.healthy = Sain -channel-type.netatmo.healthindex.state.option.fine = Agréable -channel-type.netatmo.healthindex.state.option.fair = Correct -channel-type.netatmo.healthindex.state.option.poor = Mauvais -channel-type.netatmo.healthindex.state.option.unhealthy = Malsain - -channel-type.netatmo.homecity.label = Ville -channel-type.netatmo.homecity.description = Ville - -channel-type.netatmo.homecountry.label = Pays -channel-type.netatmo.homecountry.description = Pays - -channel-type.netatmo.hometimezone.label = Fuseau horaire -channel-type.netatmo.hometimezone.description = Fuseau horaire - -channel-type.netatmo.homepersoncount.label = Compteur de personnes -channel-type.netatmo.homepersoncount.description = Nombre de personnes qui sont à la maison - -channel-type.netatmo.homeunknowncount.label = Compteur de personnes inconnues -channel-type.netatmo.homeunknowncount.description = Nombre de personnes inconnues qui sont à la maison - -channel-type.netatmo.type.label = Type de l'évènement -channel-type.netatmo.type.description = Type du dernier évènement - -channel-type.netatmo.time.label = Horodatage de l'évènement -channel-type.netatmo.time.description = Date/Heure du dernier évènement - -channel-type.netatmo.camera_id.label = ID caméra -channel-type.netatmo.camera_id.description = Caméra à l'origine du dernier évènement - -channel-type.netatmo.person_id.label = ID personne -channel-type.netatmo.person_id.description = Id de la personne concernée par le dernier évènement - -channel-type.netatmo.snapshot_url.label = URL image capturée -channel-type.netatmo.snapshot_url.description = Url de l'image capturée (quand une image est associée au dernier évènement) - -channel-type.netatmo.snapshot.label = Image capturée -channel-type.netatmo.snapshot.description = Image capturée (quand une image est associée au dernier évènement) - -channel-type.netatmo.video_url.label = URL vidéo capturée -channel-type.netatmo.video_url.description = Url de la vidéo capturée (quand une vidéo est associée au dernier évènement) - -channel-type.netatmo.video_status.label = Etat de la vidéo -channel-type.netatmo.video_status.description = Etat de la vidéo associée au dernier évènement (recording, deleted or available) - -channel-type.netatmo.is_arrival.label = Personne arrivant -channel-type.netatmo.is_arrival.description = Si cet évènement indique la détection d'une personne qui était considérée comme absente auparavant - -channel-type.netatmo.message.label = Message de l'évènement -channel-type.netatmo.message.description = Message correspondant au dernier évènement - -channel-type.netatmo.sub_type.label = Sous-type de l'évènement -channel-type.netatmo.sub_type.description = Sous-type du dernier évènement (disponible uniquement pour certains évènements) - -channel-type.netatmo.status.label = Etat de la caméra -channel-type.netatmo.status.description = Etat de la caméra - -channel-type.netatmo.sd_status.label = Etat de la carte SD -channel-type.netatmo.sd_status.description = Etat de la carte SD - -channel-type.netatmo.alim_status.label = Etat de l'alimentation -channel-type.netatmo.alim_status.description = Etat de l'alimentation - -channel-type.netatmo.is_locale.label = Caméra locale -channel-type.netatmo.is_locale.description = Indique si la caméra est dans le même réseau local que le logiciel - -channel-type.netatmo.live_picture_url.label = URL image en direct -channel-type.netatmo.live_picture_url.description = Url de l'image en direct de la caméra - -channel-type.netatmo.live_picture.label = Image en direct -channel-type.netatmo.live_picture.description = Image en direct de la caméra - -channel-type.netatmo.live_stream_url.label = URL flux vidéo en direct -channel-type.netatmo.live_stream_url.description = Url du flux vidéo en direct de la caméra - -channel-type.netatmo.last_seen.label = Date dernière détection -channel-type.netatmo.last_seen.description = Date où cette personne a été reconnue pour la dernière fois - -channel-type.netatmo.person_athome.label = A la maison -channel-type.netatmo.person_athome.description = Indique si cette personne est connue comme étant ou non à la masioon - -channel-type.netatmo.person_eventmsg.label = Dernier message -channel-type.netatmo.person_eventmsg.description = Dernier message relatif à cette personne - -channel-type.netatmo.person_eventtime.label = Date dernier message -channel-type.netatmo.person_eventtime.description = Date du dernier message relatif à cette personne - -channel-type.netatmo.person_avatar_url.label = URL avatar -channel-type.netatmo.person_avatar_url.description = Url de l'avatar de la personne - -channel-type.netatmo.person_avatar.label = Avatar -channel-type.netatmo.person_avatar.description = Avatar de la personne - -channel-type.netatmo.person_event.label = Dernière image -channel-type.netatmo.person_event.description = Image associée au dernier évènement relatif à cette personne - -channel-type.netatmo.person_event_url.label = URL de la dernière image -channel-type.netatmo.person_event_url.description = URL de l'mage associée au dernier évènement relatif à cette personne - -# Thing channels - -thing-type.netatmo.NAMain.channel.WifiStatus.label = Niveau Wifi -thing-type.netatmo.NAMain.channel.WifiStatus.description = Indicateur de la qualité de signal Wifi - -thing-type.netatmo.NAPlug.channel.WifiStatus.label = Niveau Wifi -thing-type.netatmo.NAPlug.channel.WifiStatus.description = Indicateur de la qualité de signal Wifi - -thing-type.netatmo.NAModule1.channel.RfStatus.label = Niveau radio -thing-type.netatmo.NAModule1.channel.RfStatus.description = Indicateur de la qualité de signal radio -thing-type.netatmo.NAModule1.channel.BatteryVP.label = Niveau piles -thing-type.netatmo.NAModule1.channel.BatteryVP.description = Indicateur du niveau de piles -thing-type.netatmo.NAModule1.channel.LowBattery.label = Piles faibles -thing-type.netatmo.NAModule1.channel.LowBattery.description = Indicateur du niveau faible des piles - -thing-type.netatmo.NAModule2.channel.RfStatus.label = Niveau radio -thing-type.netatmo.NAModule2.channel.RfStatus.description = Indicateur de la qualité de signal radio -thing-type.netatmo.NAModule2.channel.BatteryVP.label = Niveau piles -thing-type.netatmo.NAModule2.channel.BatteryVP.description = Indicateur du niveau de piles -thing-type.netatmo.NAModule2.channel.LowBattery.label = Piles faibles -thing-type.netatmo.NAModule2.channel.LowBattery.description = Indicateur du niveau faible des piles - -thing-type.netatmo.NAModule3.channel.RfStatus.label = Niveau radio -thing-type.netatmo.NAModule3.channel.RfStatus.description = Indicateur de la qualité de signal radio -thing-type.netatmo.NAModule3.channel.BatteryVP.label = Niveau piles -thing-type.netatmo.NAModule3.channel.BatteryVP.description = Indicateur du niveau de piles -thing-type.netatmo.NAModule3.channel.LowBattery.label = Piles faibles -thing-type.netatmo.NAModule3.channel.LowBattery.description = Indicateur du niveau faible des piles - -thing-type.netatmo.NAModule4.channel.RfStatus.label = Niveau radio -thing-type.netatmo.NAModule4.channel.RfStatus.description = Indicateur de la qualité de signal radio -thing-type.netatmo.NAModule4.channel.BatteryVP.label = Niveau piles -thing-type.netatmo.NAModule4.channel.BatteryVP.description = Indicateur du niveau de piles -thing-type.netatmo.NAModule4.channel.LowBattery.label = Piles faibles -thing-type.netatmo.NAModule4.channel.LowBattery.description = Indicateur du niveau faible des piles - -thing-type.netatmo.NATherm1.channel.RfStatus.label = Niveau radio -thing-type.netatmo.NATherm1.channel.RfStatus.description = Indicateur de la qualité de signal radio -thing-type.netatmo.NATherm1.channel.BatteryVP.label = Niveau piles -thing-type.netatmo.NATherm1.channel.BatteryVP.description = Indicateur du niveau de piles -thing-type.netatmo.NATherm1.channel.LowBattery.label = Piles faibles -thing-type.netatmo.NATherm1.channel.LowBattery.description = Indicateur du niveau faible des piles diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/aircare.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/aircare.xml new file mode 100644 index 0000000000000..7d6e5ed4a0fdc --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/aircare.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + Moment when temperature was measured at its minimum today. + + + + Moment when temperature was measured at its maximum today. + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/bridge.xml deleted file mode 100644 index b3591dfe04ede..0000000000000 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/bridge.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - 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..13c6e55effbcd 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,419 +4,246 @@ 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 - - - - - Location - - Location of the device - - - - - Number:Temperature - - Current temperature - Temperature - - - - - Number:Temperature - - Minimum Temperature on current day - Temperature - - - - - Number:Temperature - - Minimum Temperature this week - Temperature - - - - - Number:Temperature - - Minimum Temperature this month - Temperature - - - - - Number:Temperature - - Maximum Temperature on current day - Temperature - - - - - Number:Temperature - - Maximum Temperature this week - Temperature - - - - - Number:Temperature - - Maximum Temperature this month - Temperature - - - - - String - - Temperature Evolution Trend - Temperature - - - - - - - - - - - Number:Temperature - - Thermostat temperature setpoint - Temperature - - - - - String - - Chosen setpoint_mode (program, away, hg, manual, off, max) - - - - - - - - - - - - - + Switch - - Indicates whether the furnace is heating or not - - - - - Number - - Physical orientation of the thermostat module - - - - - DateTime - - Timestamp when data was measured - Time - + + Monitoring state of the camera - - DateTime - - Last Plug Seen - Time + + Contact + + Windows of the room are opened. + Window - - 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 + + Switch + + Anticipates next scheduled setpoint. - - DateTime - - Date when minimum humidity was reached on current day - Time + + Contact + + Is the furnace currently heating? - - DateTime - - Date when minimum humidity was reached this week - Time - + + trigger + + + + + - - DateTime - - Date when minimum humidity was reached this month - Time + + String + + Description of the battery status. - - DateTime - - Date when maximum humidity was reached on current day - Time + + String + + URL of the event recording. - - DateTime - - Date when maximum humidity was reached this week - Time - + + String + + Planning currently applied when following weekly schedule. + - - DateTime - - Date when maximum humidity was reached this month - Time - + + Number + + Total number of persons that are at home. + + Status + Presence + + - - DateTime - - Date when minimum noise was reached on current day - Time - + + Number + + Total number of unknown persons that are at home. + - - DateTime - - Date when minimum noise was reached this week - Time + + Image + + Snapshot of unknown person that is at home. - - DateTime - - Date when minimum noise was reached this month - Time - + + Number:Length + + Quantity of water over the period. + Rain + - - DateTime - - Date when maximum noise was reached on current day - Time - + + Number:Speed + + Current precipitation intensity. + Rain + - - DateTime - - Date when maximum noise was reached this week + + Number:Time + + Default duration of manual setpoint changes. Time - + - - DateTime - - Date when maximum noise was reached this month - Time - + + String + + State of the floodlight (On/Off/Auto) + + + + + + + - - DateTime - - Date when minimum pressure was reached on current day - Time - + + String + + Evolution of the measure over time. + Line + + + + + + + - - DateTime - - Date when minimum pressure was reached this week - Time - + + String + + State of the SD card + + + + + + + + + + + - - DateTime - - Date when minimum pressure was reached this month - Time - + + String + + State of the power connector + + + + + + - - DateTime - - Date when maximum pressure was reached on current day - Time - + + String + + Status of the video (recording, deleted or available). + + + + + + + - - DateTime - - Date when maximum pressure was reached this week - Time - + + Number:Temperature + + Thermostat temperature setpoint. + Temperature + + Setpoint + Temperature + + - - DateTime - - Date when maximum pressure was reached this month - Time - + + String + + Chosen thermostat mode (home, frost guard, manual, max). + + + + + + + + - - DateTime - - Month of the last available thermostat bilan - Time - + + String + + Chosen mode for the house (schedule, away, frost guard, manual). + + + + + + + + - - Switch - - + + Number:Dimensionless + + Percentage of heating power. + Energy + - + DateTime - - Last Message emitted by the module + + Moment when data was measured. Time - + DateTime - - Thermostat goes back to schedule after that timestamp. - Time - - - - - DateTime - - Last Them Seen + + Moment when data was measured. Time @@ -424,359 +251,276 @@ 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 + Air Quality indicator. Carbondioxide + + Measurement + CO2 + Number:Dimensionless - Current Noise Level - Noise - - - - - Number:Dimensionless - - Minimum Noise on current day - Noise - - - - - Number:Dimensionless - - Minimum Noise this week - Noise - - - - - Number:Dimensionless - - Minimum Noise this month - Noise + Current Noise Level. + SoundVolume + + Measurement + 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) - + 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 + + Computed Humidex: felt temperature. + - - Number:Pressure - - Maximum Pressure this week - Pressure - + + Number + + Appreciation of the Humidex. + + + + + + + + + - - Number:Pressure - - Maximum Pressure this month - Pressure - + + String + + Description of the event. + + + + + + + + + + + + + + + + + + + + + + + - + String - - Pressure evolution trend for last 12h (up, down, stable) - Pressure - + + Details of the event. + - - - + + + + + + + + + + + + + + + - + + trigger + + + + + + + + String - - Heat planning currently used - + + Message sent by Netatmo corresponding to given event. + - - Number:Pressure - - Absolute pressure - Pressure - + + Image + + Capture image of the event. + - - Number:Dimensionless - - Current humidity - Humidity - + + String + + Url of the event snapshot. + - - Number:Dimensionless - - Minimum Humidity on current day - Humidity - + + String + + - - Number:Dimensionless - - Minimum Humidity this week - Humidity - + + String + + ID of the camera that triggered the event. + - - Number:Dimensionless - - Minimum Humidity this month - Humidity - + + String + + URL for the avatar of this person. + - - Number:Dimensionless - - Maximum Humidity on current day - Humidity - + + Image + + Avatar of this person. + - - Number:Dimensionless - - Minimum Humidity this week - Humidity - + + Switch + + Indicates if this person is known to be at home or not. - - Number:Dimensionless - - Maximum Humidity this month - Humidity - + + Number:Power + + Signal strength indicator. + QualityOfService + - - Number - - Computed Humidex index - Temperature - + + Number:Pressure + + Pressure measured relative to a full vacuum. + Pressure + + Measurement + Pressure + + - + Number:Temperature - Computed Heat Index + Apparent computed temperature (based on temperature and humidity). Temperature - + Number:Temperature - Computed Dewpoint Temperature + Temperature to which air must be cooled to become saturated with water vapor. Temperature - + Number:Temperature - Computed Dewpoint Depression + Difference between the temperature and the dewpoint. - - 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:Temperature + + Maximum Temperature on current day. + Temperature + - - Number:Length - - Quantity of water this month - Rain - + + Number:Temperature + + Minimum Temperature on current day + Temperature + - + Number:Angle - - Current 5 minutes average wind direction + + Direction of the last 5 minutes highest gust wind Wind - + Number:Speed - - Current 5 minutes average wind speed + + Speed of the last 5 minutes highest gust wind Wind - + Number:Speed Maximum wind strength recorded @@ -784,57 +528,44 @@ - - DateTime - - Timestamp when MaxWindStrength was recorded. - Time - - - - - Number:Angle - - Direction of the last 5 minutes highest gust wind - Wind - - - - - Number:Speed - - Speed of the last 5 minutes highest gust wind - Wind - + + Location + + Location of the device + - - trigger - - Home event - - - - - - - - - - - - - - - - - - - - - - - + + String + + URL of the live stream for this camera. + + + + + Defines quality level of the feed (the higher the more bandwidth) + + + + + + poor + + + + + + Image + + Camera Live Snapshot. + + + + + String + + URL of the live snapshot for this camera (need scope access_camera). + 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..1fd7424fa7046 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/common.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Last time the module reported its presence. + + + + + + + + + + Moment of the last measures update. + + + + Last time the module reported its presence. + + + + + 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..8a5870745a30b --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/energy.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Start time of the currently applied setpoint. + + + + End time of the currently applied setpoint. + + + + + + + + + + + + + + + + + + + + End time of the currently applied setpoint. + + + + + 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..60d3a7b42c7fb --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/security.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Local URL of the live snapshot for this camera. + + + + URL of the live snapshot for this camera through Netatmo VPN. + + + + Local URL of the live stream for this camera. + + + + URL of the live stream for this camera through Netatmo VPN. + + + + + + + + + + + + + + + Moment when event occurred. + + + + + + Local URL of the event recording. + + + + URL of the event recording through Netatmo VPN. + + + + + + + + + + Last event message from this person. + + + + Moment of the last event for this person. + + + Picture of the last event for this person. + + + URL for the picture of the last event for this person. + + + + + + + + + + + + + + Moment when this person was last seen. + + + + + + + + + + + + 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..3e23183a81470 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/weather.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + Moment when temperature was measured at its minimum today. + + + + Moment when temperature was measured at its maximum today. + + + + + + + + + + + + + + + + + + + Moment when temperature was measured at its minimum today. + + + + Moment when temperature was measured at its maximum today. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Quantity of water over last hour. + + + + Quantity of water during the current day. + + + + + + + + + + + + + Moment when max wind strength was recorded. + + + + + + + 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/MeasureTest.java b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/MeasureTest.java new file mode 100644 index 0000000000000..87ed22ea44e34 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/MeasureTest.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toQuantityType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; +import org.openhab.core.types.State; + +/** + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class MeasureTest { + + @Test + public void testMeasurePrecision() { + State value = toQuantityType(25.0, MeasureClass.INSIDE_TEMPERATURE); + assertEquals("25 °C", value.toString()); + value = toQuantityType(52.0, MeasureClass.INSIDE_TEMPERATURE); + assertEquals("50 °C", value.toString()); + value = toQuantityType(-10.0, MeasureClass.INSIDE_TEMPERATURE); + assertEquals("0 °C", value.toString()); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/dto/NAObjectTest.java b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/dto/NAObjectTest.java new file mode 100644 index 0000000000000..d9a82b319cc32 --- /dev/null +++ b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/api/dto/NAObjectTest.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.netatmo.internal.api.dto; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +import java.time.ZoneId; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.openhab.binding.netatmo.internal.api.NetatmoException; +import org.openhab.binding.netatmo.internal.api.data.EventType; +import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.TrendDescription; +import org.openhab.binding.netatmo.internal.deserialization.NADeserializer; +import org.openhab.core.i18n.TimeZoneProvider; + +/** + * @author Gaël L'hopital - Initial contribution + */ +public class NAObjectTest { + private static NADeserializer gson; + + @BeforeAll + public static void init() { + TimeZoneProvider timeZoneProvider = mock(TimeZoneProvider.class); + when(timeZoneProvider.getTimeZone()).thenReturn(ZoneId.systemDefault()); + gson = new NADeserializer(timeZoneProvider); + } + + @Test + public void testNAObject() throws Exception { + String naObject = "{id:\"5954e7f249c75f97428b7b23\",name:\"Your House\"}"; + NAObject object = gson.deserialize(NAObject.class, naObject); + assertEquals(object.getName(), "Your House"); + assertEquals(object.getId(), "5954e7f249c75f97428b7b23"); + } + + @Test + public void testWebHookEvent() throws NetatmoException { + String event = "{" + " \"user_id\": \"5c810xxxxxxx45f4\"," + " \"snapshot_id\": \"5d19bxxxxxx6380342\"," + + " \"snapshot_key\": \"f0134210ff83fxxxxxxxf770090a423d9a5\"," + + " \"snapshot_url\": \"https://netatmocameraimage.blob.core.windows.net/production/5d1xxxa5\"," + + " \"event_type\": \"movement\"," + " \"camera_id\": \"70:exxxxxdd:a7\"," + + " \"device_id\": \"70:exxxxdd:a7\"," + " \"home_id\": \"5c5d79xxxx08cd594\"," + + " \"home_name\": \"Boulogne Billan.\"," + " \"event_id\": \"5d19baae369359e896380341\"," + + " \"message\": \"Boulogne Billan: Movement detected by Indoor Camera\"," + + " \"push_type\": \"NACamera-movement\"" + "}"; + WebhookEvent object = gson.deserialize(WebhookEvent.class, event); + assertEquals(object.getEventType(), EventType.MOVEMENT); + } + + @Test + public void testDashboardData() throws NetatmoException { + String dashboard = "{time_utc:1623160336,Temperature:22.1,CO2:511," + + "Humidity:66,Noise:36,Pressure:1026.1,AbsolutePressure:1009.3," + + "min_temp:20,max_temp:22.4,date_max_temp:1623147932," + + "Sdate_min_temp:1623125249,pressure_trend:\"nonexistent\",temp_trend:\"stable\"}"; + Dashboard object = gson.deserialize(Dashboard.class, dashboard); + assertEquals(511, object.getCo2(), 0); + assertEquals(TrendDescription.UNKNOWN, object.getPressureTrend()); + assertEquals(TrendDescription.STABLE, object.getTempTrend()); + } +} diff --git a/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryServiceTest.java b/bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/discovery/NetatmoModuleDiscoveryServiceTest.java deleted file mode 100644 index 9e7d837f4f798..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.discovery; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; -import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.i18n.LocaleProvider; -import org.openhab.core.i18n.TranslationProvider; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ThingUID; - -import io.swagger.client.model.NAMain; -import io.swagger.client.model.NAStationDataBody; -import io.swagger.client.model.NAStationModule; - -/** - * @author Sven Strohschein - Initial contribution - */ -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -public class NetatmoModuleDiscoveryServiceTest { - - private NetatmoModuleDiscoveryServiceAccessible service; - private NetatmoBridgeHandler bridgeHandlerSpy; - - @BeforeEach - public void before() { - Bridge bridgeMock = mock(Bridge.class); - when(bridgeMock.getUID()).thenReturn(new ThingUID("netatmo", "bridge")); - - bridgeHandlerSpy = spy(new NetatmoBridgeHandler(bridgeMock, null)); - - LocaleProvider localeProviderMock = mock(LocaleProvider.class); - TranslationProvider translationProvider = mock(TranslationProvider.class); - - service = new NetatmoModuleDiscoveryServiceAccessible(bridgeHandlerSpy, localeProviderMock, - translationProvider); - } - - @Test - public void testStartScanNothingActivated() { - service.startScan(); - - assertEquals(0, service.getDiscoveredThings().size()); - } - - @Test - public void testStartScanDiscoverWeatherStationNoStationsBody() { - activateDiscoveryWeatherStation(); - - service.startScan(); - - assertEquals(0, service.getDiscoveredThings().size()); - } - - @Test - public void testStartScanDiscoverWeatherStationNoStations() { - activateDiscoveryWeatherStation(); - - when(bridgeHandlerSpy.getStationsDataBody(null)).thenReturn(Optional.of(new NAStationDataBody())); - service.startScan(); - - assertEquals(0, service.getDiscoveredThings().size()); - } - - @Test - public void testStartScanDiscoverWeatherStationNoStationName() { - recordStationBody(createStation()); - - service.startScan(); - - List 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 a9d25592351d8..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.presence; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNull; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; -import org.openhab.binding.netatmo.internal.NetatmoBindingConstants; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.internal.ThingImpl; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; - -import io.swagger.client.model.NAWelcomeCamera; - -/** - * @author Sven Strohschein - Initial contribution - */ -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -public class NAPresenceCameraHandlerTest { - - private static final String DUMMY_VPN_URL = "https://dummytestvpnaddress.net/restricted/10.255.89.96/9826069dc689e8327ac3ed2ced4ff089/MTU5MTgzMzYwMDrQ7eHHhG0_OJ4TgmPhGlnK7QQ5pZ,,"; - private static final String DUMMY_LOCAL_URL = "http://192.168.178.76/9826069dc689e8327ac3ed2ced4ff089"; - private static final Optional 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 a6fa5327c2248..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-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.netatmo.internal.welcome; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNull; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; -import org.openhab.binding.netatmo.internal.NetatmoBindingConstants; -import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler; -import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.internal.ThingImpl; -import org.openhab.core.types.UnDefType; - -import io.swagger.client.model.NAWelcomeEvent; -import io.swagger.client.model.NAWelcomeHome; -import io.swagger.client.model.NAWelcomeHomeData; -import io.swagger.client.model.NAWelcomeSubEvent; - -/** - * @author Sven Strohschein - Initial contribution - */ -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -public class NAWelcomeHomeHandlerTest { - - private static final String DUMMY_HOME_ID = "1"; - - private NAWelcomeHomeHandlerAccessible handler; - - private @Mock NetatmoBridgeHandler bridgeHandlerMock; - private @Mock TimeZoneProvider timeZoneProviderMock; - - @BeforeEach - public void before() { - Thing welcomeHomeThing = new ThingImpl(new ThingTypeUID("netatmo", "NAWelcomeHome"), "1"); - handler = new NAWelcomeHomeHandlerAccessible(welcomeHomeThing); - } - - @Test - public void testUpdateReadingsWithEvents() { - NAWelcomeEvent event1 = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.PERSON); - NAWelcomeEvent event2 = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT); - - NAWelcomeHome home = new NAWelcomeHome(); - home.setId(DUMMY_HOME_ID); - home.setEvents(Arrays.asList(event1, event2)); - - NAWelcomeHomeData homeData = new NAWelcomeHomeData(); - homeData.setHomes(Collections.singletonList(home)); - - when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData)); - - handler.updateReadings(); - - // the second (last) event is expected - assertEquals(new StringType("movement"), - handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - - home.setEvents(Arrays.asList(event2, event1)); - // the second (last) event is still expected (independent from the order of these are added) - assertEquals(new StringType("movement"), - handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - } - - @Test - public void testUpdateReadingsWith1Event() { - NAWelcomeEvent event = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.PERSON); - - NAWelcomeHome home = new NAWelcomeHome(); - home.setId(DUMMY_HOME_ID); - home.setEvents(Collections.singletonList(event)); - - NAWelcomeHomeData homeData = new NAWelcomeHomeData(); - homeData.setHomes(Collections.singletonList(home)); - - when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData)); - - handler.updateReadings(); - - assertEquals(new StringType("person"), - handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - } - - @Test - public void testUpdateReadingsNoEvents() { - NAWelcomeHome home = new NAWelcomeHome(); - home.setId(DUMMY_HOME_ID); - - NAWelcomeHomeData homeData = new NAWelcomeHomeData(); - homeData.setHomes(Collections.singletonList(home)); - - when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData)); - - handler.updateReadings(); - - assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - } - - @Test - public void testUpdateReadingsEmptyHomeData() { - NAWelcomeHomeData homeData = new NAWelcomeHomeData(); - - when(bridgeHandlerMock.getWelcomeDataBody(any())).thenReturn(Optional.of(homeData)); - - handler.updateReadings(); - - assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - } - - @Test - public void testUpdateReadingsNoHomeData() { - handler.updateReadings(); - - assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - } - - @Test - public void testTriggerChannelIfRequired() { - NAWelcomeEvent event1 = createPresenceEvent(1592661881, NAWelcomeSubEvent.TypeEnum.ANIMAL); - NAWelcomeEvent event2 = createPresenceEvent(1592661882, NAWelcomeSubEvent.TypeEnum.HUMAN); - NAWelcomeEvent event3 = createEvent(1592661883, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT); - - NAWelcomeHome home = new NAWelcomeHome(); - home.setId(DUMMY_HOME_ID); - home.setEvents(Collections.singletonList(event1)); - - NAWelcomeHomeData homeData = new NAWelcomeHomeData(); - homeData.setHomes(Collections.singletonList(home)); - - when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData)); - - triggerCameraEvents(); - - // No triggered event is expected, because the binding is just started (with existing events). - assertEquals(0, handler.getTriggerChannelCount()); - - home.setEvents(Arrays.asList(event1, event2)); - - triggerCameraEvents(); - - // 1 triggered event is expected, because there is 1 new event since binding start (outdoor / detected human). - assertEquals(1, handler.getTriggerChannelCount()); - assertEquals(new StringType("outdoor"), - handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - assertEquals("HUMAN", handler.getLastDetectedObject()); - - home.setEvents(Arrays.asList(event1, event2)); - - triggerCameraEvents(); - - // No new triggered event is expected, because there are still the same events as before the refresh. - assertEquals(1, handler.getTriggerChannelCount()); - assertEquals(new StringType("outdoor"), - handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - assertEquals("HUMAN", handler.getLastDetectedObject()); - - home.setEvents(Arrays.asList(event1, event2, event3)); - - triggerCameraEvents(); - - // 1 new triggered event is expected (2 in sum), because there is 1 new event since the last triggered event - // (movement after outdoor / detected human). - assertEquals(2, handler.getTriggerChannelCount()); - assertEquals(new StringType("movement"), - handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - assertEquals("MOVEMENT", handler.getLastDetectedObject()); - } - - @Test - public void testTriggerChannelIfRequiredNoEventAvailable() { - NAWelcomeHome home = new NAWelcomeHome(); - home.setId(DUMMY_HOME_ID); - - NAWelcomeHomeData homeData = new NAWelcomeHomeData(); - homeData.setHomes(Collections.singletonList(home)); - - when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData)); - - triggerCameraEvents(); - - // No triggered event is expected, because there aren't any events (the collection is NULL) - assertEquals(0, handler.getTriggerChannelCount()); - - home.setEvents(Collections.emptyList()); - - triggerCameraEvents(); - - // No triggered event is expected, because there aren't any events (the collection is empty) - assertEquals(0, handler.getTriggerChannelCount()); - } - - @Test - public void testTriggerChannelIfRequiredPersonMovement() { - NAWelcomeHome home = initHome(); - - NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT); - event.setPersonId("1"); - - home.getEvents().add(event); - - triggerCameraEvents(); - - assertEquals(1, handler.getTriggerChannelCount()); - assertEquals(new StringType("movement"), - handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - assertEquals("HUMAN", handler.getLastDetectedObject()); - } - - @Test - public void testTriggerChannelIfRequiredHumanMovement() { - NAWelcomeHome home = initHome(); - - NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT); - event.setCategory(NAWelcomeEvent.CategoryEnum.HUMAN); - - home.getEvents().add(event); - - triggerCameraEvents(); - - assertEquals(1, handler.getTriggerChannelCount()); - assertEquals(new StringType("movement"), - handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - assertEquals("HUMAN", handler.getLastDetectedObject()); - } - - @Test - public void testTriggerChannelIfRequiredAnimalMovement() { - NAWelcomeHome home = initHome(); - - NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT); - event.setCategory(NAWelcomeEvent.CategoryEnum.ANIMAL); - - home.getEvents().add(event); - - triggerCameraEvents(); - - assertEquals(1, handler.getTriggerChannelCount()); - assertEquals(new StringType("movement"), - handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - assertEquals("ANIMAL", handler.getLastDetectedObject()); - } - - @Test - public void testTriggerChannelIfRequiredVehicleMovement() { - NAWelcomeHome home = initHome(); - - NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT); - event.setCategory(NAWelcomeEvent.CategoryEnum.VEHICLE); - - home.getEvents().add(event); - - triggerCameraEvents(); - - assertEquals(1, handler.getTriggerChannelCount()); - assertEquals(new StringType("movement"), - handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE)); - assertEquals("VEHICLE", handler.getLastDetectedObject()); - } - - @Test - public void testMatchDetectedObjectEnums() { - assertArrayEquals(Arrays.stream(NAWelcomeEvent.CategoryEnum.values()).map(Enum::name).toArray(), - Arrays.stream(NAWelcomeSubEvent.TypeEnum.values()).map(Enum::name).toArray(), - "The detected object enums aren't equal anymore, that could lead to a bug! Please check the usages!"); - } - - private NAWelcomeHome initHome() { - NAWelcomeEvent initLastEvent = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT); - - NAWelcomeHome home = new NAWelcomeHome(); - home.setId(DUMMY_HOME_ID); - - List 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; - } - } -}