== 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
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:
* `<USERNAME>` The username you use to connect to the Netatmo API (usually your mail address).
* `<PASSWORD>` The password attached to the above username.
+The binding has the following configuration options:
-### 2. Bridge and Things Configuration
+| Parameter | Type | Description |
+|--------------|---------------|--------------------------------------------------------------------------------------------|
+| features | String | The perimeter of functionalities given to the binding WEATHER, AIR_CARE, ENERGY, SECURITY |
+| readFriends | Boolean | Enables or disables the discovery of guest weather stations. |
-Once you will get needed informations from the Netatmo API, you will be able to configure bridge and things.
-E.g.
+## Bridge Configuration
+
+You will have to create at first a bridge to handle communication with your Netatmo Application.
+
+The Account bridge has the following configuration options:
+
+- **clientId:** Client ID provided for the application you created on http://dev.netatmo.com/createapp.
+- **clientSecret:** Client Secret provided for the application you created.
+- **username:** Your Netatmo API username (email).
+- **password:** Your Netatmo API password.
+- **webHookUrl:** Protocol, public IP and port to access openHAB server from Internet.
+- **reconnectInterval:** The reconnection interval to Netatmo API (in s).
+
+
+## List of supported things
+
+| Thing Type | Type | Netatmo Object | Description | Thing Parameters |
+|-----------------|--------|----------------|------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
+| account | Bridge | N/A | This bridge represents an account, gateway to Netatmo API. | clientId, clientSecret, username, password, webHookUrl, reconnectInterval |
+| home | Bridge | NAHome | A home hosting Security or Energy devices and modules. | id, refreshInterval |
+| person | Thing | NAPerson | A person known by your Netatmo system. | id |
+| welcome | Thing | NACamera | The Netatmo Smart Indoor Camera (Welcome). | id |
+| presence | Thing | NOC | The Netatmo Smart Outdoor Camera (Presence) camera with or without siren. | id |
+| siren | Thing | NIS | The Netatmo Smart Indoor Siren. | id |
+| doorbell | Thing | NDB | The Netatmo Smart Video Doorbell device. | id |
+| weather-station | Bridge | NAMain | Main indoor module reporting temperature, humidity, pressure, air quality and sound level. | id |
+| outdoor | Thing | NAModule1 | Outdoor module reporting temperature and humidity. | id |
+| wind | Thing | NAModule2 | Wind sensor reporting wind angle and strength. | id |
+| rain | Thing | NAModule3 | Rain Gauge measuring precipitation. | id |
+| indoor | Thing | NAModule4 | Additional indoor module reporting temperature, humidity and CO2 level. | id |
+| home-coach | Thing | NHC | Healthy home coach reporting health-index, temperature, humidity, pressure, air quality, sound level.| id |
+| plug | Thing | NAPlug | The relay connected to the boiler controlling a Thermostat and zero or more valves. | id |
+| thermostat | Thing | NATherm1 | The Thermostat device placed in a given room. | id |
+| room | Thing | NARoom | A room in your house. | id |
+| valve | Thing | NRV | A valve controlling a radiator. | id |
+
-```
-Bridge netatmo:netatmoapi:home [ clientId="<CLIENT_ID>", clientSecret="<CLIENT_SECRET>", username = "<USERNAME>", password = "<PASSWORD>", readStation=true|false, readHealthyHomeCoach=true|false, readThermostat=true|false, readWelcome=true|false, readPresence=true|false] {
- Thing NAMain inside [ id="aa:aa:aa:aa:aa:aa" ]
- Thing NAModule1 outside [ id="yy:yy:yy:yy:yy:yy", parentId="aa:aa:aa:aa:aa:aa" ]
- Thing NHC homecoach [ id="cc:cc:cc:cc:cc:cc", [refreshInterval=60000] ]
- Thing NAPlug plugtherm [ id="bb:bb:bb:bb:bb:bb", [refreshInterval=60000] ]
- Thing NATherm1 thermostat [ id="xx:xx:xx:xx:xx:xx", parentId="bb:bb:bb:bb:bb:bb" ]
- Thing NAWelcomeHome home [ id="58yyacaaexxxebca99x999x", refreshInterval=600000 ]
- Thing NACamera camera [ id="cc:cc:cc:cc:cc:cc", parentId="58yyacaaexxxebca99x999x" ]
- Thing NOC presenceOutdoorCamera [ id="dd:dd:dd:dd:dd:dd", parentId="58yyacaaexxxebca99x999x" ]
- Thing NAWelcomePerson sysadmin [ id="aaaaaaaa-bbbb-cccc-eeee-zzzzzzzzzzzz", parentId="58yyacaaexxxebca99x999x" ]
- ...
-}
-```
### Webhook
-For Welcome or Presence Camera, Netatmo servers can send push notifications to the Netatmo Binding by using a callback URL.
-The webhook URL is setup at bridge level using "Webhook Address" parameter.
+Netatmo servers can send push notifications to the Netatmo Binding by using a callback URL.
+The webhook URL is setup at binding level using "Webhook Address" parameter.
You will define here public way to access your openHAB server:
```
-http(s)://xx.yy.zz.ww:8080
+http(s)://xx.yy.zz.ww:443
```
-Your Netatmo App will be configured automatically by the bridge to the endpoint :
+Your Netatmo App will be configured automatically by the bridge to the endpoint:
```
-http(s)://xx.yy.zz.ww:8080/netatmo/%id%/camera
+http(s)://xx.yy.zz.ww:443/netatmo
```
-where %id% is the id of your camera thing.
-
Please be aware of Netatmo own limits regarding webhook usage that lead to a 24h ban-time when webhook does not answer 5 times.
+NB: Allowed ports for webhooks are 80, 88, 443 and 9443.
+
### Configure Things
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.
## 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
Weather station does not need any refreshInterval setting.
Based on a standard update period of 10mn by Netatmo systems - it will auto adapt to stick closest as possible to last data availability.
-Example item for the **indoor module**:
-
-```
-Number Netatmo_Indoor_CO2 "CO2" <carbondioxide> { channel = "netatmo:NAMain:home:inside:Co2" }
-```
**Supported channels for the main indoor module:**
-| Channel ID | Item Type | Description |
-|---------------------|----------------------|----------------------------------------------------------|
-| Co2 | Number:Dimensionless | Air quality |
-| MinCo2 | Number:Dimensionless | Minimum CO2 on current day |
-| MinCo2ThisWeek | Number:Dimensionless | Minimum CO2 this week |
-| MinCo2ThisMonth | Number:Dimensionless | Minimum CO2 this month |
-| MaxCo2 | Number:Dimensionless | Maximum CO2 on current day |
-| MaxCo2ThisWeek | Number:Dimensionless | Maximum CO2 this week |
-| MaxCo2ThisMonth | Number:Dimensionless | Maximum CO2 this month |
-| DateMinCo2 | DateTime | Date when minimum CO2 was reached on current day |
-| DateMinCo2ThisWeek | DateTime | Date when minimum CO2 was reached this week |
-| DateMinCo2ThisMonth | DateTime | Date when minimum CO2 was reached this month |
-| DateMaxCo2 | DateTime | Date when maximum CO2 was reached on current day |
-| DateMaxCo2ThisWeek | DateTime | Date when maximum CO2 was reached this week |
-| DateMaxCo2ThisMonth | DateTime | Date when maximum CO2 was reached this month |
-| Temperature | Number:Temperature | Current temperature |
-| TempTrend | String | Temperature evolution trend (up, down, stable) |
-| Noise | Number:Dimensionless | Current noise level |
-| MinNoise | Number:Dimensionless | Minimum noise on current day |
-| MinNoiseThisWeek | Number:Dimensionless | Minimum noise this week |
-| MinNoiseThisMonth | Number:Dimensionless | Minimum noise this month |
-| MaxNoise | Number:Dimensionless | Maximum noise on current day |
-| MaxNoiseThisWeek | Number:Dimensionless | Maximum noise this week |
-| MaxNoiseThisMonth | Number:Dimensionless | Maximum noise this month |
-| DateMinNoise | DateTime | Date when minimum noise was reached on current day |
-| DateMinNoiseThisWeek| DateTime | Date when minimum noise was reached this week |
-| DateMinNoiseThisMonth| DateTime | Date when minimum noise was reached this month |
-| DateMaxNoise | DateTime | Date when maximum noise was reached on current day |
-| DateMaxNoiseThisWeek| DateTime | Date when maximum noise was reached this week |
-| DateMaxNoiseThisMonth| DateTime | Date when maximum noise was reached this month |
-| Pressure | Number:Pressure | Current pressure |
-| MinPressure | Number:Pressure | Minimum pressure on current day |
-| MinPressureThisWeek | Number:Pressure | Minimum pressure this week |
-| MinPressureThisMonth| Number:Pressure | Minimum pressure this month |
-| MaxPressure | Number:Pressure | Maximum pressure on current day |
-| MaxPressureThisWeek | Number:Pressure | Maximum pressure this week |
-| MaxPressureThisMonth| Number:Pressure | Maximum pressure this month |
-| DateMinPressure | DateTime | Date when minimum pressure was reached on current day |
-| DateMinPressureThisWeek | DateTime | Date when minimum pressure was reached this week |
-| DateMinPressureThisMonth| DateTime | Date when minimum pressure was reached this month |
-| DateMaxPressure | DateTime | Date when maximum pressure was reached on current day |
-| DateMaxPressureThisWeek | DateTime | Date when maximum pressure was reached this week |
-| DateMaxPressureThisMonth| DateTime | Date when maximum pressure was reached this month |
-| PressTrend | String | Pressure evolution trend for last 12h (up, down, stable) |
-| AbsolutePressure | Number:Pressure | Absolute pressure |
-| Humidity | Number:Dimensionless | Current humidity |
-| MinHumidity | Number:Dimensionless | Minimum humidity on current day |
-| MinHumidityThisWeek | Number:Dimensionless | Minimum humidity this week |
-| MinHumidityThisMonth| Number:Dimensionless | Minimum humidity this month |
-| MaxHumidity | Number:Dimensionless | Maximum humidity on current day |
-| MaxHumidityThisWeek | Number:Dimensionless | Maximum humidity this week |
-| MaxHumidityThisMonth| Number:Dimensionless | Maximum humidity this month |
-| DateMinHumidity | DateTime | Date when minimum humidity was reached on current day |
-| DateMinHumidityThisWeek | DateTime | Date when minimum humidity was reached this week |
-| DateMinHumidityThisMonth| DateTime | Date when minimum humidity was reached this month |
-| DateMaxHumidity | DateTime | Date when maximum humidity was reached on current day |
-| DateMaxHumidityThisWeek | DateTime | Date when maximum humidity was reached this week |
-| DateMaxHumidityThisMonth| DateTime | Date when maximum humidity was reached this month |
-| Humidex | Number | Computed Humidex index |
-| HeatIndex | Number:Temperature | Computed Heat Index |
-| Dewpoint | Number:Temperature | Computed dewpoint temperature |
-| DewpointDepression | Number:Temperature | Computed dewpoint depression |
-| MinTemp | Number:Temperature | Minimum temperature on current day |
-| MinTempThisWeek | Number:Temperature | Minimum temperature this week |
-| MinTempThisMonth | Number:Temperature | Minimum temperature this month |
-| MaxTemp | Number:Temperature | Maximum temperature on current day |
-| MaxTempThisWeek | Number:Temperature | Maximum temperature this week |
-| MaxTempThisMonth | Number:Temperature | Maximum temperature this month |
-| DateMinTemp | DateTime | Date when minimum temperature was reached on current day |
-| DateMinTempThisWeek | DateTime | Date when minimum temperature was reached this week |
-| DateMinTempThisMonth| DateTime | Date when minimum temperature was reached this month |
-| DateMaxTemp | DateTime | Date when maximum temperature was reached on current day |
-| DateMaxTempThisWeek | DateTime | Date when maximum temperature was reached this week |
-| DateMaxTempThisMonth| DateTime | Date when maximum temperature was reached this month |
-| DateMinTemp | DateTime | Date when minimum temperature was reached on current day |
-| DateMaxTemp | DateTime | Date when maximum temperature was reached on current day |
-| TimeStamp | DateTime | Timestamp when data was measured |
-| LastStatusStore | DateTime | Last status store |
-| WifiStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
-| Location | Location | Location of the device |
+| Channel Group | Channel Id | Item Type | Description |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| pressure | value | Number:Pressure | Current pressure |
+| pressure | absolute | Number:Pressure | Pressure at sea level |
+| pressure | trend | String | Pressure evolution trend over time |
+| noise | value | Number:Dimensionless | Current noise level |
+| humidity | value | Number:Dimensionless | Current humidity |
+| humidity | humidex | Number | Computed Humidex index |
+| humidity | humidex-scale | Number | Humidex index appreciation |
+| temperature | value | Number:Temperature | Current temperature |
+| temperature | min-today | Number:Temperature | Minimum temperature on current day |
+| temperature | max-today | Number:Temperature | Maximum temperature on current day |
+| temperature | min-time | DateTime | Moment of today's minimum temperature |
+| temperature | max-time | DateTime | Moment of today's maximum temperature |
+| temperature | trend | String | Temperature evolution trend over time |
+| temperature | heat-index | Number:Temperature | Computed Heat Index |
+| temperature | dewpoint | Number:Temperature | Computed dewpoint temperature |
+| temperature | dewpoint-depression | Number:Temperature | Computed dewpoint depression |
+| airquality | co2 | Number:Dimensionless | CO2 level in ppm |
+| location | value | Location | Location of the device |
+| timestamp | last-seen | DateTime | Last time the module reported its presence |
+| timestamp | measures | DateTime | Moment of the last measures update |
+| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) |
+| signal | value | Number:Power | Signal strength in dBm |
All these channels are read only.
### Weather Station Outdoor module
-Example item for the **outdoor module**
-
-```
-Number Netatmo_Outdoor_Temperature "Temperature" { channel = "netatmo:NAModule1:home:outside:Temperature" }
-```
-
**Supported channels for the outdoor module:**
-| Channel ID | Item Type | Description |
-|---------------------|----------------------|----------------------------------------------------------|
-| Temperature | Number:Temperature | Current temperature |
-| TempTrend | String | Temperature evolution trend (up, down, stable) |
-| Humidity | Number:Dimensionless | Current humidity |
-| MinHumidity | Number:Dimensionless | Minimum humidity on current day |
-| MinHumidityThisWeek | Number:Dimensionless | Minimum humidity this week |
-| MinHumidityThisMonth| Number:Dimensionless | Minimum humidity this month |
-| MaxHumidity | Number:Dimensionless | Maximum humidity on current day |
-| MaxHumidityThisWeek | Number:Dimensionless | Maximum humidity this week |
-| MaxHumidityThisMonth| Number:Dimensionless | Maximum humidity this month |
-| DateMinHumidity | DateTime | Date when minimum humidity was reached on current day |
-| DateMinHumidityThisWeek | DateTime | Date when minimum humidity was reached this week |
-| DateMinHumidityThisMonth| DateTime | Date when minimum humidity was reached this month |
-| DateMaxHumidity | DateTime | Date when maximum humidity was reached on current day |
-| DateMaxHumidityThisWeek | DateTime | Date when maximum humidity was reached this week |
-| DateMaxHumidityThisMonth| DateTime | Date when maximum humidity was reached this month |
-| Humidex | Number | Computed Humidex index |
-| HeatIndex | Number:Temperature | Computed Heat Index |
-| Dewpoint | Number:Temperature | Computed dewpoint temperature |
-| DewpointDepression | Number:Temperature | Computed dewpoint depression |
-| MinTemp | Number:Temperature | Minimum temperature on current day |
-| MinTempThisWeek | Number:Temperature | Minimum temperature this week |
-| MinTempThisMonth | Number:Temperature | Minimum temperature this month |
-| MaxTemp | Number:Temperature | Maximum temperature on current day |
-| MaxTempThisWeek | Number:Temperature | Maximum temperature this week |
-| MaxTempThisMonth | Number:Temperature | Maximum temperature this month |
-| DateMinTemp | DateTime | Date when minimum temperature was reached on current day |
-| DateMinTempThisWeek | DateTime | Date when minimum temperature was reached this week |
-| DateMinTempThisMonth| DateTime | Date when minimum temperature was reached this month |
-| DateMaxTemp | DateTime | Date when maximum temperature was reached on current day |
-| DateMaxTempThisWeek | DateTime | Date when maximum temperature was reached this week |
-| DateMaxTempThisMonth| DateTime | Date when maximum temperature was reached this month |
-| TimeStamp | DateTime | Timestamp when data was measured |
-| LastMessage | DateTime | Last message emitted by the module |
-| LowBattery | Switch | Low battery |
-| BatteryVP | Number | Battery level |
-| RfStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
+| Channel Group | Channel Id | Item Type | Description |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| humidity | value | Number:Dimensionless | Current humidity |
+| humidity | humidex | Number | Computed Humidex index |
+| humidity | humidex-scale | Number | Humidex index appreciation |
+| temperature | value | Number:Temperature | Current temperature |
+| temperature | min-today | Number:Temperature | Minimum temperature on current day |
+| temperature | max-today | Number:Temperature | Maximum temperature on current day |
+| temperature | min-time | DateTime | Moment of today's minimum temperature |
+| temperature | max-time | DateTime | Moment of today's maximum temperature |
+| temperature | trend | String | Temperature evolution trend over time |
+| temperature | heat-index | Number:Temperature | Computed Heat Index |
+| temperature | dewpoint | Number:Temperature | Computed dewpoint temperature |
+| temperature | dewpoint-depression | Number:Temperature | Computed dewpoint depression |
+| timestamp | last-seen | DateTime | Last time the module reported its presence |
+| timestamp | measures | DateTime | Moment of the last measures update |
+| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) |
+| signal | value | Number:Power | Signal strength in dBm |
+| battery | value | Number | Battery level |
+| battery | low-battery | Switch | Low battery |
All these channels are read only.
### Weather Station Additional Indoor module
-Example item for the **indoor module**
-
-```
-Number Netatmo_Indoor2_Temperature "Temperature" { channel = "netatmo:NAModule4:home:insidesupp:Temperature" }
-```
**Supported channels for the additional indoor module:**
-| Channel ID | Item Type | Description |
-|---------------------|----------------------|----------------------------------------------------------|
-| Co2 | Number:Dimensionless | Air quality |
-| MinCo2 | Number:Dimensionless | Minimum CO2 on current day |
-| MinCo2ThisWeek | Number:Dimensionless | Minimum CO2 this week |
-| MinCo2ThisMonth | Number:Dimensionless | Minimum CO2 this month |
-| MaxCo2 | Number:Dimensionless | Maximum CO2 on current day |
-| MaxCo2ThisWeek | Number:Dimensionless | Maximum CO2 this week |
-| MaxCo2ThisMonth | Number:Dimensionless | Maximum CO2 this month |
-| DateMinCo2 | DateTime | Date when minimum CO2 was reached on current day |
-| DateMinCo2ThisWeek | DateTime | Date when minimum CO2 was reached this week |
-| DateMinCo2ThisMonth | DateTime | Date when minimum CO2 was reached this month |
-| DateMaxCo2 | DateTime | Date when maximum CO2 was reached on current day |
-| DateMaxCo2ThisWeek | DateTime | Date when maximum CO2 was reached this week |
-| DateMaxCo2ThisMonth | DateTime | Date when maximum CO2 was reached this month |
-| Temperature | Number:Temperature | Current temperature |
-| TempTrend | String | Temperature evolution trend (up, down, stable) |
-| Humidity | Number:Dimensionless | Current humidity |
-| MinHumidity | Number:Dimensionless | Minimum humidity on current day |
-| MinHumidityThisWeek | Number:Dimensionless | Minimum humidity this week |
-| MinHumidityThisMonth| Number:Dimensionless | Minimum humidity this month |
-| MaxHumidity | Number:Dimensionless | Maximum humidity on current day |
-| MaxHumidityThisWeek | Number:Dimensionless | Maximum humidity this week |
-| MaxHumidityThisMonth| Number:Dimensionless | Maximum humidity this month |
-| DateMinHumidity | DateTime | Date when minimum humidity was reached on current day |
-| DateMinHumidityThisWeek | DateTime | Date when minimum humidity was reached this week |
-| DateMinHumidityThisMonth| DateTime | Date when minimum humidity was reached this month |
-| DateMaxHumidity | DateTime | Date when maximum humidity was reached on current day |
-| DateMaxHumidityThisWeek | DateTime | Date when maximum humidity was reached this week |
-| DateMaxHumidityThisMonth| DateTime | Date when maximum humidity was reached this month |
-| Humidex | Number | Computed Humidex index |
-| HeatIndex | Number:Temperature | Computed Heat Index |
-| Dewpoint | Number:Temperature | Computed dewpoint temperature |
-| DewpointDepression | Number:Temperature | Computed dewpoint depression |
-| MinTemp | Number:Temperature | Minimum temperature on current day |
-| MinTempThisWeek | Number:Temperature | Minimum temperature this week |
-| MinTempThisMonth | Number:Temperature | Minimum temperature this month |
-| MaxTemp | Number:Temperature | Maximum temperature on current day |
-| MaxTempThisWeek | Number:Temperature | Maximum temperature this week |
-| MaxTempThisMonth | Number:Temperature | Maximum temperature this month |
-| DateMinTemp | DateTime | Date when minimum temperature was reached on current day |
-| DateMinTempThisWeek | DateTime | Date when minimum temperature was reached this week |
-| DateMinTempThisMonth| DateTime | Date when minimum temperature was reached this month |
-| DateMaxTemp | DateTime | Date when maximum temperature was reached on current day |
-| DateMaxTempThisWeek | DateTime | Date when maximum temperature was reached this week |
-| DateMaxTempThisMonth| DateTime | Date when maximum temperature was reached this month |
-| TimeStamp | DateTime | Timestamp when data was measured |
-| LastMessage | DateTime | Last message emitted by the module |
-| LowBattery | Switch | Low battery |
-| BatteryVP | Number | Battery level |
-| RfStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
+| Channel Group | Channel Id | Item Type | Description |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| humidity | value | Number:Dimensionless | Current humidity |
+| humidity | humidex | Number | Computed Humidex index |
+| humidity | humidex-scale | Number | Humidex index appreciation |
+| temperature | value | Number:Temperature | Current temperature |
+| temperature | min-today | Number:Temperature | Minimum temperature on current day |
+| temperature | max-today | Number:Temperature | Maximum temperature on current day |
+| temperature | min-time | DateTime | Moment of today's minimum temperature |
+| temperature | max-time | DateTime | Moment of today's maximum temperature |
+| temperature | trend | String | Temperature evolution trend over time |
+| temperature | heat-index | Number:Temperature | Computed Heat Index |
+| temperature | dewpoint | Number:Temperature | Computed dewpoint temperature |
+| temperature | dewpoint-depression | Number:Temperature | Computed dewpoint depression |
+| airquality | co2 | Number:Dimensionless | Air quality |
+| timestamp | last-seen | DateTime | Last time the module reported its presence |
+| timestamp | measures | DateTime | Moment of the last measures update |
+| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) |
+| signal | value | Number:Power | Signal strength in dBm |
+| battery | value | Number | Battery level |
+| battery | low-battery | Switch | Low battery |
All these channels are read only.
### Rain Gauge
-Example item for the **rain gauge**
-
-```
-Number Netatmo_Rain_Current "Rain [%.1f mm]" { channel = "netatmo:NAModule3:home:rain:Rain" }
-```
**Supported channels for the rain guage:**
-| Channel ID | Item Type | Description |
-|---------------------|---------------|----------------------------------------------------------|
-| Rain | Number:Length | Quantity of water |
-| SumRain1 | Number:Length | Quantity of water on last hour |
-| SumRain24 | Number:Length | Quantity of water on last day |
-| SumRainThisWeek | Number:Length | Quantity of water this week |
-| SumRainThisMonth | Number:Length | Quantity of water this month |
-| TimeStamp | DateTime | Timestamp when data was measured |
-| LastMessage | DateTime | Last message emitted by the module |
-| LowBattery | Switch | Low battery |
-| BatteryVP | Number | Battery level |
-| RfStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
+| Channel Group | Channel Id | Item Type | Description |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| rain | value | Number:Speed | Current precipitation intensity |
+| rain | sum-1 | Number:Length | Quantity of water over last hour |
+| rain | sum-24 | Number:Length | Quantity of water during the current day |
+| timestamp | last-seen | DateTime | Last time the module reported its presence |
+| timestamp | measures | DateTime | Moment of the last measures update |
+| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) |
+| signal | value | Number:Power | Signal strength in dBm |
+| battery | value | Number | Battery level |
+| battery | low-battery | Switch | Low battery |
All these channels are read only.
### Weather Station Wind module
-Example item for the **wind module**:
-
-```
-Number Netatmo_Wind_Strength "Wind Strength [%.0f KPH]" { channel = "netatmo:NAModule2:home:wind:WindStrength" }
-```
**Supported channels for the wind module:**
-| Channel ID | Item Type | Description |
-|---------------------|--------------|----------------------------------------------------------|
-| WindAngle | Number:Angle | Current 5 minutes average wind direction |
-| WindStrength | Number:Speed | Current 5 minutes average wind speed |
-| GustAngle | Number:Angle | Direction of the last 5 minutes highest gust wind |
-| GustStrength | Number:Speed | Speed of the last 5 minutes highest gust wind |
-| TimeStamp | DateTime | Timestamp when data was measured |
-| LastMessage | DateTime | Last message emitted by the module |
-| LowBattery | Switch | Low battery |
-| BatteryVP | Number | Battery level |
-| RfStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
-| MaxWindStrength | Number:Speed | Maximum wind strength recorded |
-| DateMaxWindStrength | DateTime | Timestamp when MaxWindStrength was recorded |
+| Channel Group | Channel Id | Item Type | Description |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| wind | angle | Number:Angle | Current 5 minutes average wind direction |
+| wind | strength | Number:Speed | Current 5 minutes average wind speed |
+| wind | max-strength | Number:Speed | Maximum wind strength recorded |
+| wind | max-strength-date | DateTime | Moment when MaxWindStrength was recorded |
+| wind | gust-angle | Number:Angle | Direction of the last 5 minutes highest gust |
+| wind | gust-strength | Number:Speed | Speed of the last 5 minutes highest gust wind |
+| timestamp | last-seen | DateTime | Last time the module reported its presence |
+| timestamp | measures | DateTime | Moment of the last measures update |
+| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) |
+| signal | value | Number:Power | Signal strength in dBm |
+| battery | value | Number | Battery level |
+| battery | low-battery | Switch | Low battery |
All these channels are read only.
### Healthy Home Coach Device
-Example item for the **Healthy Home Coach**:
-
-```
-String Netatmo_LivingRoom_HomeCoach_HealthIndex "Climate" { channel = "netatmo:NHC:home:livingroom:HealthIndex" }
-```
**Supported channels for the healthy home coach device:**
-| Channel ID | Item Type | Description |
-|---------------------|----------------------|----------------------------------------------------------|
-| HealthIndex | String | Health index (healthy, fine, fair, poor, unhealthy) |
-| Co2 | Number:Dimensionless | Air quality |
-| Temperature | Number:Temperature | Current temperature |
-| TempTrend | String | Temperature evolution trend (up, down, stable) |
-| Noise | Number:Dimensionless | Current noise level |
-| Pressure | Number:Pressure | Current pressure |
-| PressTrend | String | Pressure evolution trend for last 12h (up, down, stable) |
-| AbsolutePressure | Number:Pressure | Absolute pressure |
-| Humidity | Number:Dimensionless | Current humidity |
-| MinTemp | Number:Temperature | Minimum temperature on current day |
-| MaxTemp | Number:Temperature | Maximum temperature on current day |
-| DateMinTemp | DateTime | Date when minimum temperature was reached on current day |
-| DateMaxTemp | DateTime | Date when maximum temperature was reached on current day |
-| TimeStamp | DateTime | Timestamp when data was measured |
-| LastStatusStore | DateTime | Last status store |
-| WifiStatus | Number | Signal strength (0 for no signal, 1 for weak, 2 for average, 3 for good or 4 for excellent) |
-| Location | Location | Location of the device |
+| Channel Group | Channel Id | Item Type | Description |
+|---------------------|----------------------|----------------------|--------------------------------------------------|
+| noise | value | Number:Dimensionless | Current noise level |
+| humidity | value | Number:Dimensionless | Current humidity |
+| humidity | humidex | Number | Computed Humidex index |
+| humidity | humidex-scale | Number | Humidex index appreciation |
+| pressure | value | Number:Pressure | Current pressure |
+| pressure | absolute | Number:Pressure | Pressure at sea level |
+| temperature | value | Number:Temperature | Current temperature |
+| temperature | min-today | Number:Temperature | Minimum temperature on current day |
+| temperature | max-today | Number:Temperature | Maximum temperature on current day |
+| temperature | min-time | DateTime | Moment of today's minimum temperature |
+| temperature | max-time | DateTime | Moment of today's maximum temperature |
+| temperature | heat-index | Number:Temperature | Computed Heat Index |
+| temperature | dewpoint | Number:Temperature | Computed dewpoint temperature |
+| temperature | dewpoint-depression | Number:Temperature | Computed dewpoint depression |
+| airquality | health-index | Number | Health index (*) |
+| airquality | co2 | Number:Dimensionless | Air quality |
+| timestamp | last-seen | DateTime | Last time the module reported its presence |
+| timestamp | measures | DateTime | Moment of the last measures update |
+| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) |
+| signal | value | Number:Power | Signal strength in dBm |
+
+(*) Health index values :
+
+- 0 : healthy
+- 1 : fine
+- 2 : fair
+- 3 : poor
+- 4 : unhealthy
All these channels are read only.
**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
**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
**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
**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
## things/netatmo.things
```
-// Bridge configuration:
-Bridge netatmo:netatmoapi:home "Netatmo API" [ clientId="*********", clientSecret="**********", username = "mail@example.com", password = "******", readStation=true, readThermostat=false] {
- // Thing configuration:
- Thing NAMain inside "Netatmo Inside" [ id="aa:aa:aa:aa:aa:aa" ]
- Thing NAModule1 outside "Netatmo Outside" [ id="bb:bb:bb:bb:bb:bb", parentId="aa:aa:aa:aa:aa:aa" ]
- Thing NAModule3 rain "Netatmo Rain" [ id="cc:cc:cc:cc:cc:cc", parentId="aa:aa:aa:aa:aa:aa" ]
+Bridge netatmo:account:home "Netatmo Account" [clientId="", clientSecret="", username="", password=""] {
+ Bridge weather-station inside "Inside Weather Station" [id="70:ee:aa:aa:aa:aa"] {
+ outdoor outside "Outside Module" [id="02:00:00:aa:aa:aa"] {
+ Channels:
+ Type hum-measurement : maxHumWeek [limit="MAX",period="1week"]
+ }
+ rain rainModule "Rain Module" [id="05:00:00:aa:aa:aa"] {
+ Channels:
+ Type sum_rain-measurement: rainThisWeek "Rain This Week" [period="1week"]
+ Type sum_rain-measurement: rainThisMonth "Rain This Month" [period="1month"]
+ }
+ }
}
```
+
+## Sample configuration of live-stream-url channels:
+
+```
+ ....
+ Thing welcome camera "Caméra" [ id="xxxxxx" ] {
+ Channels:
+ Type live-stream-url : live#local-stream-url [ quality="high" ]
+ Type live-stream-url : live#vpn-stream-url [ quality="low" ]
+ }
+ ...
+```
+
+
## items/netatmo.items
```
# Indoor Module
-Number:Temperature Indoor_Temp "Temperature [%.1f %unit%]" <temperature> { channel = "netatmo:NAMain:home:inside:Temperature" }
-Number:Temperature Indoor_Min_Temp "Min Temperature Today [%.1f %unit%]" <temperature> { channel = "netatmo:NAMain:home:inside:MinTemp" }
-Number:Temperature Indoor_Min_Temp_This_Week "Min Temperature This Week [%.1f %unit%]" <temperature> { channel = "netatmo:NAMain:home:inside:MinTempThisWeek" }
-Number:Temperature Indoor_Min_Temp_This_Month "Min Temperature This Month [%.1f %unit%]" <temperature> { channel = "netatmo:NAMain:home:inside:MinTempThisMonth" }
-Number:Temperature Indoor_Max_Temp "Max Temperature Today [%.1f %unit%]" <temperature> { channel = "netatmo:NAMain:home:inside:MaxTemp" }
-Number:Temperature Indoor_Max_Temp_This_Week "Max Temperature This Week [%.1f %unit%]" <temperature> { channel = "netatmo:NAMain:home:inside:MaxTempThisWeek" }
-Number:Temperature Indoor_Max_Temp_This_Month "Max Temperature This Month [%.1f %unit%]" <temperature> { channel = "netatmo:NAMain:home:inside:MaxTempThisMonth" }
-DateTime Indoor_Min_Temp_TS "Min Temperature Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinTemp" }
-DateTime Indoor_Min_Temp_This_Week_TS "Min Temperature This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinTempThisWeek" }
-DateTime Indoor_Min_Temp_This_Month_TS "Min Temperature This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinTempThisMonth" }
-DateTime Indoor_Max_Temp_TS "Max Temperature Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxTemp" }
-DateTime Indoor_Max_Temp_This_Week_TS "Max Temperature This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxTempThisWeek" }
-DateTime Indoor_Max_Temp_This_Month_TS "Max Temperature This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxTempThisMonth" }
-Number:Dimensionless Indoor_Humidity "Humidity [%d %unit%]" <humidity> { channel = "netatmo:NAMain:home:inside:Humidity" }
-Number:Dimensionless Indoor_Min_Humidity "Min Humidity Today [%d %unit%]" <humidity> { channel = "netatmo:NAMain:home:inside:MinHumidity" }
-Number:Dimensionless Indoor_Min_Humidity_This_Week "Min Humidity This Week [%d %unit%]" <humidity> { channel = "netatmo:NAMain:home:inside:MinHumidityThisWeek" }
-Number:Dimensionless Indoor_Min_Humidity_This_Month "Min Humidity This Month [%d %unit%]" <humidity> { channel = "netatmo:NAMain:home:inside:MinHumidityThisMonth" }
-Number:Dimensionless Indoor_Max_Humidity "Max Humidity Today [%d %unit%]" <humidity> { channel = "netatmo:NAMain:home:inside:MaxHumidity" }
-Number:Dimensionless Indoor_Max_Humidity_This_Week "Max Humidity This Week [%d %unit%]" <humidity> { channel = "netatmo:NAMain:home:inside:MaxHumidityThisWeek" }
-Number:Dimensionless Indoor_Max_Humidity_This_Month "Max Humidity This Month [%d %unit%]" <humidity> { channel = "netatmo:NAMain:home:inside:MaxHumidityThisMonth" }
-DateTime Indoor_Min_Humidity_TS "Min Humidity Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinHumidity" }
-DateTime Indoor_Min_Humidity_This_Week_TS "Min Humidity This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinHumidityThisWeek" }
-DateTime Indoor_Min_Humidity_This_Month_TS "Min Humidity This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinHumidityThisMonth" }
-DateTime Indoor_Max_Humidity_TS "Max Humidity Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxHumidity" }
-DateTime Indoor_Max_Humidity_This_Week_TS "Max Humidity This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxHumidityThisWeek" }
-DateTime Indoor_Max_Humidity_This_Month_TS "Max Humidity This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxHumidityThisMonth" }
-Number Indoor_Humidex "Humidex [%.0f]" <temperature_hot> { channel = "netatmo:NAMain:home:inside:Humidex" }
-Number:Temperature Indoor_HeatIndex "HeatIndex [%.1f %unit%]" <temperature_hot> { channel = "netatmo:NAMain:home:inside:HeatIndex" }
-Number:Temperature Indoor_Dewpoint "Dewpoint [%.1f %unit%]" <temperature_cold> { channel = "netatmo:NAMain:home:inside:Dewpoint" }
-Number:Temperature Indoor_DewpointDepression "DewpointDepression [%.1f %unit%]" <temperature_cold> { channel = "netatmo:NAMain:home:inside:DewpointDepression" }
-Number:Dimensionless Indoor_Co2 "CO2 [%d %unit%]" <carbondioxide> { channel = "netatmo:NAMain:home:inside:Co2" }
-Number:Dimensionless Indoor_Min_Co2 "Min CO2 Today [%.1f %unit%]" <carbondioxide> { channel = "netatmo:NAMain:home:inside:MinCo2" }
-Number:Dimensionless Indoor_Min_Co2_This_Week "Min CO2 This Week [%.1f %unit%]" <carbondioxide> { channel = "netatmo:NAMain:home:inside:MinCo2ThisWeek" }
-Number:Dimensionless Indoor_Min_Co2_This_Month "Min CO2 This Month [%.1f %unit%]" <carbondioxide> { channel = "netatmo:NAMain:home:inside:MinCo2ThisMonth" }
-Number:Dimensionless Indoor_Max_Co2 "Max CO2 Today [%.1f %unit%]" <carbondioxide> { channel = "netatmo:NAMain:home:inside:MaxCo2" }
-Number:Dimensionless Indoor_Max_Co2_This_Week "Max CO2 This Week [%.1f %unit%]" <carbondioxide> { channel = "netatmo:NAMain:home:inside:MaxCo2ThisWeek" }
-Number:Dimensionless Indoor_Max_Co2_This_Month "Max CO2 This Month [%.1f %unit%]" <carbondioxide> { channel = "netatmo:NAMain:home:inside:MaxCo2ThisMonth" }
-DateTime Indoor_Min_Co2_TS "Min CO2 Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinCo2" }
-DateTime Indoor_Min_Co2_This_Week_TS "Min CO2 This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinCo2ThisWeek" }
-DateTime Indoor_Min_Co2_This_Month_TS "Min CO2 This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinCo2ThisMonth" }
-DateTime Indoor_Max_Co2_TS "Max CO2 Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxCo2" }
-DateTime Indoor_Max_Co2_This_Week_TS "Max CO2 This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxCo2ThisWeek" }
-DateTime Indoor_Max_Co2_This_Month_TS "Max CO2 This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxCo2ThisMonth" }
-Number:Pressure Indoor_Pressure "Pressure [%.1f %unit%]" <pressure> { channel = "netatmo:NAMain:home:inside:Pressure" }
-Number:Pressure Indoor_Min_Pressure "Min Pressure Today [%d %unit%]" <pressure> { channel = "netatmo:NAMain:home:inside:MinPressure" }
-Number:Pressure Indoor_Min_Pressure_This_Week "Min Pressure This Week [%d %unit%]" <pressure> { channel = "netatmo:NAMain:home:inside:MinPressureThisWeek" }
-Number:Pressure Indoor_Min_Pressure_This_Month "Min Pressure This Month [%d %unit%]" <pressure> { channel = "netatmo:NAMain:home:inside:MinPressureThisMonth" }
-Number:Pressure Indoor_Max_Pressure "Max Pressure Today [%d %unit%]" <pressure> { channel = "netatmo:NAMain:home:inside:MaxPressure" }
-Number:Pressure Indoor_Max_Pressure_This_Week "Max Pressure This Week [%d %unit%]" <pressure> { channel = "netatmo:NAMain:home:inside:MaxPressureThisWeek" }
-Number:Pressure Indoor_Max_Pressure_This_Month "Max Pressure This Month [%d %unit%]" <pressure> { channel = "netatmo:NAMain:home:inside:MaxPressureThisMonth" }
-DateTime Indoor_Min_Pressure_TS "Min Pressure Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinPressure" }
-DateTime Indoor_Min_Pressure_This_Week_TS "Min Pressure This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinPressureThisWeek" }
-DateTime Indoor_Min_Pressure_This_Month_TS "Min Pressure This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinPressureThisMonth" }
-DateTime Indoor_Max_Pressure_TS "Max Pressure Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxPressure" }
-DateTime Indoor_Max_Pressure_This_Week_TS "Max Pressure This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxPressureThisWeek" }
-DateTime Indoor_Max_Pressure_This_Month_TS "Max Pressure This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxPressureThisMonth" }
-Number:Pressure Indoor_AbsolutePressure "AbsolutePressure [%.1f %unit%]" <pressure> { channel = "netatmo:NAMain:home:inside:AbsolutePressure" }
-Number:Dimensionless Indoor_Noise "Noise [%d %unit%]" <soundvolume> { channel = "netatmo:NAMain:home:inside:Noise" }
-Number:Dimensionless Indoor_Min_Noise "Min Noise Today [%.1f %unit%]" <soundvolume> { channel = "netatmo:NAMain:home:inside:MinNoise" }
-Number:Dimensionless Indoor_Min_Noise_This_Week "Min Noise This Week [%.1f %unit%]" <soundvolume> { channel = "netatmo:NAMain:home:inside:MinNoiseThisWeek" }
-Number:Dimensionless Indoor_Min_Noise_This_Month "Min Noise This Month [%.1f %unit%]" <soundvolume> { channel = "netatmo:NAMain:home:inside:MinNoiseThisMonth" }
-Number:Dimensionless Indoor_Max_Noise "Max Noise Today [%.1f %unit%]" <soundvolume> { channel = "netatmo:NAMain:home:inside:MaxNoise" }
-Number:Dimensionless Indoor_Max_Noise_This_Week "Max Noise This Week [%.1f %unit%]" <soundvolume> { channel = "netatmo:NAMain:home:inside:MaxNoiseThisWeek" }
-Number:Dimensionless Indoor_Max_Noise_This_Month "Max Noise This Month [%.1f %unit%]" <soundvolume> { channel = "netatmo:NAMain:home:inside:MaxNoiseThisMonth" }
-DateTime Indoor_Min_Noise_TS "Min Noise Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinNoise" }
-DateTime Indoor_Min_Noise_This_Week_TS "Min Noise This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinNoiseThisWeek" }
-DateTime Indoor_Min_Noise_This_Month_TS "Min Noise This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMinNoiseThisMonth" }
-DateTime Indoor_Max_Noise_TS "Max Noise Today [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxNoise" }
-DateTime Indoor_Max_Noise_This_Week_TS "Max Noise This Week [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxNoiseThisWeek" }
-DateTime Indoor_Max_Noise_This_Month_TS "Max Noise This Month [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:DateMaxNoiseThisMonth" }
-Number Indoor_WifiStatus "WifiStatus [%s]" <signal> { channel = "netatmo:NAMain:home:inside:WifiStatus" }
-DateTime Indoor_TimeStamp "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAMain:home:inside:TimeStamp" }
-Location Indoor_Location "Location" <movecontrol> { channel = "netatmo:NAMain:home:inside:Location" }
-DateTime Indoor_LastStatusStore "LastStatusStore [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <text> { channel = "netatmo:NAMain:home:inside:LastStatusStore" }
+Number:Temperature Indoor_Temp "Temperature [%.1f %unit%]" <temperature> { channel = "netatmo:weather-station:home:inside:temperature#value" }
+Number:Temperature Indoor_Min_Temp "Min Temperature Today [%.1f %unit%]" <temperature> { channel = "netatmo:weather-station:home:inside:temperature#min-today" }
+Number:Temperature Indoor_Max_Temp "Max Temperature Today [%.1f %unit%]" <temperature> { channel = "netatmo:weather-station:home:inside:temperature#max-today" }
+Number:Dimensionless Indoor_Humidity "Humidity [%d %unit%]" <humidity> { channel = "netatmo:weather-station:home:inside:humidity#value" }
+Number Indoor_Humidex "Humidex [%.0f]" <temperature_hot> { channel = "netatmo:weather-station:home:inside:humidity#humidex" }
+Number:Temperature Indoor_HeatIndex "HeatIndex [%.1f %unit%]" <temperature_hot> { channel = "netatmo:weather-station:home:inside:temperature#heat-index" }
+Number:Temperature Indoor_Dewpoint "Dewpoint [%.1f %unit%]" <temperature_cold> { channel = "netatmo:weather-station:home:inside:temperature#dewpoint" }
+Number:Temperature Indoor_DewpointDepression "DewpointDepression [%.1f %unit%]" <temperature_cold> { channel = "netatmo:weather-station:home:inside:temperature#dewpoint-depression" }
+Number:Dimensionless Indoor_Co2 "CO2 [%d %unit%]" <carbondioxide> { channel = "netatmo:weather-station:home:inside:airquality#co2" }
+Number:Pressure Indoor_Pressure "Pressure [%.1f %unit%]" <pressure> { channel = "netatmo:weather-station:home:inside:pressure#value" }
+Number:Pressure Indoor_AbsolutePressure "AbsolutePressure [%.1f %unit%]" <pressure> { channel = "netatmo:weather-station:home:inside:pressure#absolute" }
+Number:Dimensionless Indoor_Noise "Noise [%d %unit%]" <soundvolume> { channel = "netatmo:weather-station:home:inside:noise#value" }
+Number Indoor_RadioStatus "RadioStatus [%s]" <signal> { channel = "netatmo:weather-station:home:inside:signal#strength" }
+DateTime Indoor_TimeStamp "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:weather-station:home:inside:timestamp#measures" }
+DateTime Indoor_LastSeen "LastSeen [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <text> { channel = "netatmo:weather-station:home:inside:timestamp#last-seen" }
# Outdoor Module
-Number:Temperature Outdoor_Temperature "Temperature [%.1f %unit%]" <temperature> { channel = "netatmo:NAModule1:home:outside:Temperature" }
-String Outdoor_TempTrend "TempTrend [%s]" <line> { channel = "netatmo:NAModule1:home:outside:TempTrend" }
-Number:Dimensionless Outdoor_Humidity "Humidity [%d %unit%]" <humidity> { channel = "netatmo:NAModule1:home:outside:Humidity" }
-Number Outdoor_Humidex "Humidex [%.0f]" <temperature_hot> { channel = "netatmo:NAModule1:home:outside:Humidex" }
-Number:Temperature Outdoor_HeatIndex "HeatIndex [%.1f %unit%]" <temperature_hot> { channel = "netatmo:NAModule1:home:outside:HeatIndex" }
-Number:Temperature Outdoor_Dewpoint "Dewpoint [%.1f %unit%]" <temperature_cold> { channel = "netatmo:NAModule1:home:outside:Dewpoint" }
-Number:Temperature Outdoor_DewpointDepression "DewpointDepression [%.1f %unit%]" <temperature_cold> { channel = "netatmo:NAModule1:home:outside:DewpointDepression" }
-Number Outdoor_RfStatus "RfStatus [%.0f / 5]" <signal> { channel = "netatmo:NAModule1:home:outside:RfStatus" }
-Switch Outdoor_LowBattery "LowBattery [%s]" <siren> { channel = "netatmo:NAModule1:home:outside:LowBattery" }
-Number Outdoor_BatteryVP "BatteryVP [%.0f %%]" <battery> { channel = "netatmo:NAModule1:home:outside:BatteryVP" }
-DateTime Outdoor_TimeStamp "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:NAModule1:home:outside:TimeStamp" }
-DateTime Outdoor_LastMessage "LastMessage [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <text> { channel = "netatmo:NAModule1:home:outside:LastMessage" }
+Number:Temperature Outdoor_Temperature "Temperature [%.1f %unit%]" <temperature> { channel = "netatmo:outdoor:home:inside:outside:temperature#value" }
+String Outdoor_TempTrend "TempTrend [%s]" <line> { channel = "netatmo:outdoor:home:inside:outside:temperature#trend" }
+Number:Dimensionless Outdoor_Humidity "Humidity [%d %unit%]" <humidity> { channel = "netatmo:outdoor:home:inside:outside:humidity#value" }
+Number Outdoor_Humidex "Humidex [%.0f]" <temperature_hot> { channel = "netatmo:outdoor:home:inside:outside:humidity#humidex" }
+Number:Temperature Outdoor_HeatIndex "heat-index [%.1f %unit%]" <temperature_hot> { channel = "netatmo:outdoor:home:inside:outside:temperature#heat-index" }
+Number:Temperature Outdoor_Dewpoint "Dewpoint [%.1f %unit%]" <temperature_cold> { channel = "netatmo:outdoor:home:inside:outside:temperature#dewpoint" }
+Number:Temperature Outdoor_DewpointDepression "DewpointDepression [%.1f %unit%]" <temperature_cold> { channel = "netatmo:outdoor:home:inside:outside:temperature#dewpoint-depression" }
+Number Outdoor_RadioStatus "RfStatus [%.0f / 5]" <signal> { channel = "netatmo:outdoor:home:inside:outside:signal#strength" }
+Switch Outdoor_LowBattery "LowBattery [%s]" <siren> { channel = "netatmo:outdoor:home:inside:outside:battery#low-battery" }
+DateTime Outdoor_TimeStamp "Measures TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <calendar> { channel = "netatmo:outdoor:home:inside:outside:timestamp#measures" }
+DateTime Outdoor_LastMessage "LastMessage [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" <text> { channel = "netatmo:outdoor:home:inside:outside:timestamp#last-seen" }
# Rain Module
-Number:Length Rain_Hour "Rain Last Hour [%.02f %unit%]" <rain> {channel="netatmo:NAModule3:home:rain:SumRain1"}
-Number:Length Rain_Today "Rain Today [%.02f %unit%]" <rain> {channel="netatmo:NAModule3:home:rain:SumRain24"}
-Number:Length Rain_Week "Rain This Week [%.02f %unit%]" <rain> {channel="netatmo:NAModule3:home:rain:SumRainThisWeek"}
-Number:Length Rain_Month "Rain This Month [%.02f %unit%]" <rain> {channel="netatmo:NAModule3:home:rain:SumRainThisMonth"}
-Number Rain_BatteryVP "Rain battery status [%d%%]" <battery> {channel="netatmo:NAModule3:home:rain:BatteryVP"}
+Number:Speed Rain_Intensity "Rain Intensity [%.1f %unit%]" <rain> { channel = "netatmo:rain:home:inside:rainModule:rain#value"}
+Number:Length Rain_Hour "Rain Last Hour [%.1f %unit%]" <rain> { channel = "netatmo:rain:home:inside:rainModule:rain#sum-1"}
+Number:Length Rain_Today "Rain Today [%.1f %unit%]" <rain> { channel = "netatmo:rain:home:inside:rainModule:rain#sum-24"}
```
## sitemaps/netatmo.sitemap
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
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
# 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
- 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
+
+
<name>openHAB Add-ons :: Bundles :: Netatmo Binding</name>
- <properties>
- <bnd.importpackage>!android.*,!com.android.org.*,!org.apache.harmony.*,!sun.*,!org.apache.oltu.*</bnd.importpackage>
- </properties>
-
- <dependencies>
- <dependency>
- <groupId>org.openhab.osgiify</groupId>
- <artifactId>org.json.json</artifactId>
- <version>20131018</version>
- <scope>compile</scope>
- </dependency>
- <dependency>
- <groupId>com.squareup.okhttp</groupId>
- <artifactId>okhttp</artifactId>
- <version>2.7.5</version>
- <scope>compile</scope>
- <exclusions>
- <exclusion>
- <groupId>com.google.android</groupId>
- <artifactId>*</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>com.squareup.okhttp</groupId>
- <artifactId>logging-interceptor</artifactId>
- <version>2.7.5</version>
- <scope>compile</scope>
- <exclusions>
- <exclusion>
- <groupId>com.google.android</groupId>
- <artifactId>*</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>com.squareup.okio</groupId>
- <artifactId>okio</artifactId>
- <version>1.6.0</version>
- <scope>compile</scope>
- </dependency>
- <dependency>
- <groupId>io.gsonfire</groupId>
- <artifactId>gson-fire</artifactId>
- <version>1.8.4</version>
- <scope>compile</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.oltu.oauth2</groupId>
- <artifactId>org.apache.oltu.oauth2.client</artifactId>
- <version>1.0.0</version>
- <scope>compile</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.oltu.oauth2</groupId>
- <artifactId>org.apache.oltu.oauth2.common</artifactId>
- <version>1.0.0</version>
- <scope>compile</scope>
- </dependency>
- <dependency>
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
- <version>1.8</version>
- <scope>compile</scope>
- </dependency>
- <dependency>
- <groupId>com.google.code.gson</groupId>
- <artifactId>gson</artifactId>
- <version>2.8.5</version>
- <scope>compile</scope>
- </dependency>
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>io.swagger.codegen.v3</groupId>
- <artifactId>swagger-codegen-maven-plugin</artifactId>
- <version>3.0.21</version>
- <executions>
- <execution>
- <goals>
- <goal>generate</goal>
- </goals>
- <configuration>
- <inputSpec>https://raw.githubusercontent.com/cbornet/netatmo-swagger-decl/35e27745fb0d432bc6c8b5ec7a83ed2a09944cea/spec/swagger.yaml</inputSpec>
- <language>java</language>
- <generateApiTests>false</generateApiTests>
- <generateModelTests>false</generateModelTests>
- <configOptions>
- <sourceFolder>src/main/java</sourceFolder>
- <java8>true</java8>
- <dateLibrary>java8-localdatetime</dateLibrary>
- <useRuntimeException>true</useRuntimeException>
- </configOptions>
- </configuration>
- </execution>
- </executions>
- <dependencies>
- <dependency>
- <!-- Required for JDK 17 compatibility, see: https://github.com/swagger-api/swagger-codegen/issues/11253 -->
- <groupId>com.github.jknack</groupId>
- <artifactId>handlebars</artifactId>
- <version>4.3.0</version>
- </dependency>
- </dependencies>
- </plugin>
- </plugins>
- </build>
-
</project>
<feature name="openhab-binding-netatmo" description="Netatmo Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
- <bundle dependency="true">mvn:org.openhab.osgiify/org.json.json/20131018</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.netatmo/${project.version}</bundle>
</feature>
</features>
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * {@link APIUtils} provides util methods for the usage of the generated API classes.
- *
- * @author Sven Strohschein - Initial contribution
- */
-@NonNullByDefault
-public final class APIUtils {
-
- private APIUtils() {
- }
-
- public static <T> Stream<T> nonNullStream(Collection<T> collection) {
- return Optional.ofNullable(collection).stream().flatMap(Collection::stream);
- }
-
- public static <T> List<T> nonNullList(List<T> list) {
- return Optional.ofNullable(list).orElse(Collections.emptyList());
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 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;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.events.EventPublisher;
-import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
-import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
-import org.openhab.core.thing.link.ItemChannelLinkRegistry;
-import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-
-/**
- * Dynamic provider of state options for NATherm1Handler.
- *
- * @author Gregory Moyer - Initial contribution
- * @author Gaël L'hopital - Ported as-is in Netatmo binding
- */
-@Component(service = { DynamicStateDescriptionProvider.class, NATherm1StateDescriptionProvider.class })
-@NonNullByDefault
-public class NATherm1StateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
-
- @Activate
- public NATherm1StateDescriptionProvider(final @Reference EventPublisher eventPublisher, //
- final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
- final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
- this.eventPublisher = eventPublisher;
- this.itemChannelLinkRegistry = itemChannelLinkRegistry;
- this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
- }
-}
*/
package org.openhab.binding.netatmo.internal;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent.EventTypeEnum;
-import org.openhab.core.thing.ThingTypeUID;
/**
- * The {@link NetatmoBinding} class defines common constants, which are used
+ * The {@link NetatmoBindingConstants} class defines common constants, which are used
* across the whole binding.
*
* @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
*
*/
@NonNullByDefault
public class NetatmoBindingConstants {
- private static final String BINDING_ID = "netatmo";
-
+ public static final String BINDING_ID = "netatmo";
public static final String VENDOR = "Netatmo";
// Configuration keys
public static final String EQUIPMENT_ID = "id";
- public static final String PARENT_ID = "parentId";
- public static final String REFRESH_INTERVAL = "refreshInterval";
- public static final String SETPOINT_DEFAULT_DURATION = "setpointDefaultDuration";
-
- public static final String WEBHOOK_APP = "app_security";
-
- // Scale for Weather Station /getmeasure
- public static final String THIRTY_MINUTES = "30min";
- public static final String ONE_HOUR = "1hour";
- public static final String THREE_HOURS = "3hours";
- public static final String ONE_DAY = "1day";
- public static final String ONE_WEEK = "1week";
- public static final String ONE_MONTH = "1month";
-
- // Type for Weather Station /getmeasure
- public static final String DATE_MIN_CO2 = "date_min_co2";
- public static final String DATE_MAX_CO2 = "date_max_co2";
- public static final String DATE_MIN_HUM = "date_min_hum";
- public static final String DATE_MAX_HUM = "date_max_hum";
- public static final String DATE_MIN_NOISE = "date_min_noise";
- public static final String DATE_MAX_NOISE = "date_max_noise";
- public static final String DATE_MIN_PRESSURE = "date_min_pressure";
- public static final String DATE_MAX_PRESSURE = "date_max_pressure";
- public static final String DATE_MIN_TEMP = "date_min_temp";
- public static final String DATE_MAX_TEMP = "date_max_temp";
- public static final String MIN_CO2 = "min_co2";
- public static final String MAX_CO2 = "max_co2";
- public static final String MIN_HUM = "min_hum";
- public static final String MAX_HUM = "max_hum";
- public static final String MIN_NOISE = "min_noise";
- public static final String MAX_NOISE = "max_noise";
- public static final String MIN_PRESSURE = "min_pressure";
- public static final String MAX_PRESSURE = "max_pressure";
- public static final String MIN_TEMP = "min_temp";
- public static final String MAX_TEMP = "max_temp";
- public static final String SUM_RAIN = "sum_rain";
-
- // List of Bridge Type UIDs
- public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "netatmoapi");
-
- // List of Weather Station Things Type UIDs
- public static final ThingTypeUID MAIN_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAMain");
- public static final ThingTypeUID MODULE1_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule1");
- public static final ThingTypeUID MODULE2_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule2");
- public static final ThingTypeUID MODULE3_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule3");
- public static final ThingTypeUID MODULE4_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAModule4");
-
- // Netatmo Health Coach
- public static final ThingTypeUID HOMECOACH_THING_TYPE = new ThingTypeUID(BINDING_ID, "NHC");
-
- // List of Thermostat Things Type UIDs
- public static final ThingTypeUID PLUG_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAPlug");
- public static final ThingTypeUID THERM1_THING_TYPE = new ThingTypeUID(BINDING_ID, "NATherm1");
-
- // List of Welcome Home Things Type UIDs
- public static final ThingTypeUID WELCOME_HOME_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAWelcomeHome");
- public static final ThingTypeUID WELCOME_CAMERA_THING_TYPE = new ThingTypeUID(BINDING_ID, "NACamera");
- public static final ThingTypeUID WELCOME_PERSON_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAWelcomePerson");
- // Presence camera
- public static final ThingTypeUID PRESENCE_CAMERA_THING_TYPE = new ThingTypeUID(BINDING_ID, "NOC");
-
- // Weather Station Channel ids
- public static final String CHANNEL_TEMPERATURE = "Temperature";
- public static final String CHANNEL_TEMP_TREND = "TempTrend";
- public static final String CHANNEL_HUMIDITY = "Humidity";
- public static final String CHANNEL_MAX_HUMIDITY = "MaxHumidity";
- public static final String CHANNEL_MAX_HUMIDITY_THIS_WEEK = "MaxHumidityThisWeek";
- public static final String CHANNEL_MAX_HUMIDITY_THIS_MONTH = "MaxHumidityThisMonth";
- public static final String CHANNEL_MIN_HUMIDITY = "MinHumidity";
- public static final String CHANNEL_MIN_HUMIDITY_THIS_WEEK = "MinHumidityThisWeek";
- public static final String CHANNEL_MIN_HUMIDITY_THIS_MONTH = "MinHumidityThisMonth";
- public static final String CHANNEL_HUMIDEX = "Humidex";
- public static final String CHANNEL_TIMEUTC = "TimeStamp";
- public static final String CHANNEL_DEWPOINT = "Dewpoint";
- public static final String CHANNEL_DEWPOINTDEP = "DewpointDepression";
- public static final String CHANNEL_HEATINDEX = "HeatIndex";
- public static final String CHANNEL_LAST_STATUS_STORE = "LastStatusStore";
- public static final String CHANNEL_LAST_MESSAGE = "LastMessage";
- public static final String CHANNEL_LOCATION = "Location";
- public static final String CHANNEL_DATE_MAX_CO2 = "DateMaxCo2";
- public static final String CHANNEL_DATE_MAX_CO2_THIS_WEEK = "DateMaxCo2ThisWeek";
- public static final String CHANNEL_DATE_MAX_CO2_THIS_MONTH = "DateMaxCo2ThisMonth";
- public static final String CHANNEL_DATE_MIN_CO2 = "DateMinCo2";
- public static final String CHANNEL_DATE_MIN_CO2_THIS_WEEK = "DateMinCo2ThisWeek";
- public static final String CHANNEL_DATE_MIN_CO2_THIS_MONTH = "DateMinCo2ThisMonth";
- public static final String CHANNEL_DATE_MAX_HUMIDITY = "DateMaxHumidity";
- public static final String CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK = "DateMaxHumidityThisWeek";
- public static final String CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH = "DateMaxHumidityThisMonth";
- public static final String CHANNEL_DATE_MIN_HUMIDITY = "DateMinHumidity";
- public static final String CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK = "DateMinHumidityThisWeek";
- public static final String CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH = "DateMinHumidityThisMonth";
- public static final String CHANNEL_DATE_MAX_NOISE = "DateMaxNoise";
- public static final String CHANNEL_DATE_MAX_NOISE_THIS_WEEK = "DateMaxNoiseThisWeek";
- public static final String CHANNEL_DATE_MAX_NOISE_THIS_MONTH = "DateMaxNoiseThisMonth";
- public static final String CHANNEL_DATE_MIN_NOISE = "DateMinNoise";
- public static final String CHANNEL_DATE_MIN_NOISE_THIS_WEEK = "DateMinNoiseThisWeek";
- public static final String CHANNEL_DATE_MIN_NOISE_THIS_MONTH = "DateMinNoiseThisMonth";
- public static final String CHANNEL_DATE_MAX_PRESSURE = "DateMaxPressure";
- public static final String CHANNEL_DATE_MAX_PRESSURE_THIS_WEEK = "DateMaxPressureThisWeek";
- public static final String CHANNEL_DATE_MAX_PRESSURE_THIS_MONTH = "DateMaxPressureThisMonth";
- public static final String CHANNEL_DATE_MIN_PRESSURE = "DateMinPressure";
- public static final String CHANNEL_DATE_MIN_PRESSURE_THIS_WEEK = "DateMinPressureThisWeek";
- public static final String CHANNEL_DATE_MIN_PRESSURE_THIS_MONTH = "DateMinPressureThisMonth";
- public static final String CHANNEL_DATE_MAX_TEMP = "DateMaxTemp";
- public static final String CHANNEL_DATE_MAX_TEMP_THIS_WEEK = "DateMaxTempThisWeek";
- public static final String CHANNEL_DATE_MAX_TEMP_THIS_MONTH = "DateMaxTempThisMonth";
- public static final String CHANNEL_DATE_MIN_TEMP = "DateMinTemp";
- public static final String CHANNEL_DATE_MIN_TEMP_THIS_WEEK = "DateMinTempThisWeek";
- public static final String CHANNEL_DATE_MIN_TEMP_THIS_MONTH = "DateMinTempThisMonth";
- public static final String CHANNEL_MAX_TEMP = "MaxTemp";
- public static final String CHANNEL_MAX_TEMP_THIS_WEEK = "MaxTempThisWeek";
- public static final String CHANNEL_MAX_TEMP_THIS_MONTH = "MaxTempThisMonth";
- public static final String CHANNEL_MIN_TEMP = "MinTemp";
- public static final String CHANNEL_MIN_TEMP_THIS_WEEK = "MinTempThisWeek";
- public static final String CHANNEL_MIN_TEMP_THIS_MONTH = "MinTempThisMonth";
- public static final String CHANNEL_ABSOLUTE_PRESSURE = "AbsolutePressure";
- public static final String CHANNEL_CO2 = "Co2";
- public static final String CHANNEL_MAX_CO2 = "MaxCo2";
- public static final String CHANNEL_MAX_CO2_THIS_WEEK = "MaxCo2ThisWeek";
- public static final String CHANNEL_MAX_CO2_THIS_MONTH = "MaxCo2ThisMonth";
- public static final String CHANNEL_MIN_CO2 = "MinCo2";
- public static final String CHANNEL_MIN_CO2_THIS_WEEK = "MinCo2ThisWeek";
- public static final String CHANNEL_MIN_CO2_THIS_MONTH = "MinCo2ThisMonth";
- public static final String CHANNEL_NOISE = "Noise";
- public static final String CHANNEL_MAX_NOISE = "MaxNoise";
- public static final String CHANNEL_MAX_NOISE_THIS_WEEK = "MaxNoiseThisWeek";
- public static final String CHANNEL_MAX_NOISE_THIS_MONTH = "MaxNoiseThisMonth";
- public static final String CHANNEL_MIN_NOISE = "MinNoise";
- public static final String CHANNEL_MIN_NOISE_THIS_WEEK = "MinNoiseThisWeek";
- public static final String CHANNEL_MIN_NOISE_THIS_MONTH = "MinNoiseThisMonth";
- public static final String CHANNEL_PRESSURE = "Pressure";
- public static final String CHANNEL_MAX_PRESSURE = "MaxPressure";
- public static final String CHANNEL_MAX_PRESSURE_THIS_WEEK = "MaxPressureThisWeek";
- public static final String CHANNEL_MAX_PRESSURE_THIS_MONTH = "MaxPressureThisMonth";
- public static final String CHANNEL_MIN_PRESSURE = "MinPressure";
- public static final String CHANNEL_MIN_PRESSURE_THIS_WEEK = "MinPressureThisWeek";
- public static final String CHANNEL_MIN_PRESSURE_THIS_MONTH = "MinPressureThisMonth";
- public static final String CHANNEL_PRESS_TREND = "PressTrend";
- public static final String CHANNEL_RAIN = "Rain";
- public static final String CHANNEL_SUM_RAIN1 = "SumRain1";
- public static final String CHANNEL_SUM_RAIN24 = "SumRain24";
- public static final String CHANNEL_SUM_RAIN_THIS_WEEK = "SumRainThisWeek";
- public static final String CHANNEL_SUM_RAIN_THIS_MONTH = "SumRainThisMonth";
- public static final String CHANNEL_WIND_ANGLE = "WindAngle";
- public static final String CHANNEL_WIND_STRENGTH = "WindStrength";
- public static final String CHANNEL_MAX_WIND_STRENGTH = "MaxWindStrength";
- public static final String CHANNEL_DATE_MAX_WIND_STRENGTH = "DateMaxWindStrength";
- public static final String CHANNEL_GUST_ANGLE = "GustAngle";
- public static final String CHANNEL_GUST_STRENGTH = "GustStrength";
- public static final String CHANNEL_LOW_BATTERY = "LowBattery";
- public static final String CHANNEL_BATTERY_LEVEL = "BatteryVP";
- public static final String CHANNEL_WIFI_STATUS = "WifiStatus";
- public static final String CHANNEL_RF_STATUS = "RfStatus";
-
- // Healthy Home Coach specific channel
- public static final String CHANNEL_HEALTH_INDEX = "HealthIndex";
-
- // Thermostat specific channels
- public static final String CHANNEL_SETPOINT_MODE = "SetpointMode";
- public static final String CHANNEL_SETPOINT_END_TIME = "SetpointEndTime";
- public static final String CHANNEL_SETPOINT_TEMP = "Sp_Temperature";
- public static final String CHANNEL_THERM_RELAY = "ThermRelayCmd";
- public static final String CHANNEL_THERM_ORIENTATION = "ThermOrientation";
- public static final String CHANNEL_CONNECTED_BOILER = "ConnectedBoiler";
- public static final String CHANNEL_LAST_PLUG_SEEN = "LastPlugSeen";
- public static final String CHANNEL_LAST_BILAN = "LastBilan";
-
- public static final String CHANNEL_PLANNING = "Planning";
-
- public static final String CHANNEL_SETPOINT_MODE_MANUAL = "manual";
- public static final String CHANNEL_SETPOINT_MODE_AWAY = "away";
- public static final String CHANNEL_SETPOINT_MODE_HG = "hg";
- public static final String CHANNEL_SETPOINT_MODE_OFF = "off";
- public static final String CHANNEL_SETPOINT_MODE_MAX = "max";
- public static final String CHANNEL_SETPOINT_MODE_PROGRAM = "program";
-
- // Module Properties
- public static final String PROPERTY_SIGNAL_LEVELS = "signalLevels";
- public static final String PROPERTY_BATTERY_LEVELS = "batteryLevels";
- public static final String PROPERTY_REFRESH_PERIOD = "refreshPeriod";
-
- // Welcome Home specific channels
- public static final String CHANNEL_WELCOME_HOME_CITY = "welcomeHomeCity";
- public static final String CHANNEL_WELCOME_HOME_COUNTRY = "welcomeHomeCountry";
- public static final String CHANNEL_WELCOME_HOME_TIMEZONE = "welcomeHomeTimezone";
- public static final String CHANNEL_WELCOME_HOME_PERSONCOUNT = "welcomeHomePersonCount";
- public static final String CHANNEL_WELCOME_HOME_UNKNOWNCOUNT = "welcomeHomeUnknownCount";
-
- public static final String CHANNEL_WELCOME_HOME_EVENT = "welcomeHomeEvent";
-
- public static final String CHANNEL_CAMERA_EVENT = "cameraEvent";
-
- public static final String CHANNEL_WELCOME_PERSON_LASTSEEN = "welcomePersonLastSeen";
- public static final String CHANNEL_WELCOME_PERSON_ATHOME = "welcomePersonAtHome";
- public static final String CHANNEL_WELCOME_PERSON_AVATAR_URL = "welcomePersonAvatarUrl";
- public static final String CHANNEL_WELCOME_PERSON_AVATAR = "welcomePersonAvatar";
- public static final String CHANNEL_WELCOME_PERSON_LASTMESSAGE = "welcomePersonLastEventMessage";
- public static final String CHANNEL_WELCOME_PERSON_LASTTIME = "welcomePersonLastEventTime";
- public static final String CHANNEL_WELCOME_PERSON_LASTEVENT = "welcomePersonLastEvent";
- public static final String CHANNEL_WELCOME_PERSON_LASTEVENT_URL = "welcomePersonLastEventUrl";
-
- public static final String CHANNEL_WELCOME_CAMERA_STATUS = "welcomeCameraStatus";
- public static final String CHANNEL_WELCOME_CAMERA_SDSTATUS = "welcomeCameraSdStatus";
- public static final String CHANNEL_WELCOME_CAMERA_ALIMSTATUS = "welcomeCameraAlimStatus";
- public static final String CHANNEL_WELCOME_CAMERA_ISLOCAL = "welcomeCameraIsLocal";
- public static final String CHANNEL_WELCOME_CAMERA_LIVEPICTURE = "welcomeCameraLivePicture";
- public static final String CHANNEL_WELCOME_CAMERA_LIVEPICTURE_URL = "welcomeCameraLivePictureUrl";
- public static final String CHANNEL_WELCOME_CAMERA_LIVESTREAM_URL = "welcomeCameraLiveStreamUrl";
-
- public static final String CHANNEL_WELCOME_EVENT_TYPE = "welcomeEventType";
- public static final String CHANNEL_WELCOME_EVENT_TIME = "welcomeEventTime";
- public static final String CHANNEL_WELCOME_EVENT_CAMERAID = "welcomeEventCameraId";
- public static final String CHANNEL_WELCOME_EVENT_PERSONID = "welcomeEventPersonId";
- public static final String CHANNEL_WELCOME_EVENT_SNAPSHOT = "welcomeEventSnapshot";
- public static final String CHANNEL_WELCOME_EVENT_SNAPSHOT_URL = "welcomeEventSnapshotURL";
- public static final String CHANNEL_WELCOME_EVENT_VIDEO_URL = "welcomeEventVideoURL";
- public static final String CHANNEL_WELCOME_EVENT_VIDEOSTATUS = "welcomeEventVideoStatus";
- public static final String CHANNEL_WELCOME_EVENT_ISARRIVAL = "welcomeEventIsArrival";
- public static final String CHANNEL_WELCOME_EVENT_MESSAGE = "welcomeEventMessage";
- public static final String CHANNEL_WELCOME_EVENT_SUBTYPE = "welcomeEventSubType";
-
- // Camera specific channels
- public static final String CHANNEL_CAMERA_STATUS = "cameraStatus";
- public static final String CHANNEL_CAMERA_SDSTATUS = "cameraSdStatus";
- public static final String CHANNEL_CAMERA_ALIMSTATUS = "cameraAlimStatus";
- public static final String CHANNEL_CAMERA_ISLOCAL = "cameraIsLocal";
- public static final String CHANNEL_CAMERA_LIVEPICTURE = "cameraLivePicture";
- public static final String CHANNEL_CAMERA_LIVEPICTURE_URL = "cameraLivePictureUrl";
- public static final String CHANNEL_CAMERA_LIVESTREAM_URL = "cameraLiveStreamUrl";
-
- public static final String WELCOME_PICTURE_URL = "https://api.netatmo.com/api/getcamerapicture";
- public static final String WELCOME_PICTURE_IMAGEID = "image_id";
- public static final String WELCOME_PICTURE_KEY = "key";
-
- // Presence outdoor camera specific channels
- public static final String CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE = "cameraFloodlightAutoMode";
- public static final String CHANNEL_CAMERA_FLOODLIGHT = "cameraFloodlight";
-
- // List of all supported physical devices and modules
- public static final Set<ThingTypeUID> SUPPORTED_DEVICE_THING_TYPES_UIDS = Stream
- .of(MAIN_THING_TYPE, MODULE1_THING_TYPE, MODULE2_THING_TYPE, MODULE3_THING_TYPE, MODULE4_THING_TYPE,
- HOMECOACH_THING_TYPE, PLUG_THING_TYPE, THERM1_THING_TYPE, WELCOME_HOME_THING_TYPE,
- WELCOME_CAMERA_THING_TYPE, WELCOME_PERSON_THING_TYPE, PRESENCE_CAMERA_THING_TYPE)
- .collect(Collectors.toSet());
-
- // List of all adressable things in OH = SUPPORTED_DEVICE_THING_TYPES_UIDS + the virtual bridge
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
- .concat(SUPPORTED_DEVICE_THING_TYPES_UIDS.stream(), Stream.of(APIBRIDGE_THING_TYPE))
- .collect(Collectors.toSet());
- public static final Set<EventTypeEnum> HOME_EVENTS = Stream.of(EventTypeEnum.PERSON_AWAY)
- .collect(Collectors.toSet());
- public static final Set<EventTypeEnum> WELCOME_EVENTS = Stream
- .of(EventTypeEnum.PERSON, EventTypeEnum.MOVEMENT, EventTypeEnum.CONNECTION, EventTypeEnum.DISCONNECTION,
- EventTypeEnum.ON, EventTypeEnum.OFF, EventTypeEnum.BOOT, EventTypeEnum.SD, EventTypeEnum.ALIM,
- EventTypeEnum.NEW_MODULE, EventTypeEnum.MODULE_CONNECT, EventTypeEnum.MODULE_DISCONNECT,
- EventTypeEnum.MODULE_LOW_BATTERY, EventTypeEnum.MODULE_END_UPDATE, EventTypeEnum.TAG_BIG_MOVE,
- EventTypeEnum.TAG_SMALL_MOVE, EventTypeEnum.TAG_UNINSTALLED, EventTypeEnum.TAG_OPEN)
- .collect(Collectors.toSet());
- public static final Set<EventTypeEnum> PERSON_EVENTS = Stream.of(EventTypeEnum.PERSON, EventTypeEnum.PERSON_AWAY)
- .collect(Collectors.toSet());
- public static final Set<EventTypeEnum> PRESENCE_EVENTS = Stream
- .of(EventTypeEnum.OUTDOOR, EventTypeEnum.ALIM, EventTypeEnum.DAILY_SUMMARY).collect(Collectors.toSet());
+ // Things properties
+ public static final String PROPERTY_CITY = "city";
+ public static final String PROPERTY_COUNTRY = "country";
+ public static final String PROPERTY_TIMEZONE = "timezone";
+ public static final String PROPERTY_FEATURE = "feature";
+
+ // Channel group ids
+ public static final String GROUP_LAST_EVENT = "last-event";
+ public static final String GROUP_TEMPERATURE = "temperature";
+ public static final String GROUP_HUMIDITY = "humidity";
+ public static final String GROUP_AIR_QUALITY = "airquality";
+ public static final String GROUP_NOISE = "noise";
+ public static final String GROUP_PRESSURE = "pressure";
+ public static final String GROUP_TIMESTAMP = "timestamp";
+ public static final String GROUP_RAIN = "rain";
+ public static final String GROUP_WIND = "wind";
+ public static final String GROUP_ENERGY = "energy";
+ public static final String GROUP_SIGNAL = "signal";
+ public static final String GROUP_BATTERY = "battery";
+ public static final String GROUP_SECURITY = "security";
+ public static final String GROUP_CAM_STATUS = "status";
+ public static final String GROUP_CAM_LIVE = "live";
+ public static final String GROUP_PRESENCE = "presence";
+ public static final String GROUP_PERSON = "person";
+ public static final String GROUP_PERSON_EVENT = "person-event";
+ public static final String GROUP_ROOM_TEMPERATURE = "room-temperature";
+ public static final String GROUP_ROOM_PROPERTIES = "room-properties";
+ public static final String GROUP_TH_PROPERTIES = "th-properties";
+ public static final String GROUP_TH_SETPOINT = "setpoint";
+ public static final String GROUP_LOCATION = "location";
+
+ // Alternative extended groups
+ public static final String OPTION_EXTENDED = "-extended";
+ public static final String OPTION_OUTSIDE = "-outside";
+ public static final String GROUP_TYPE_TIMESTAMP_EXTENDED = GROUP_TIMESTAMP + OPTION_EXTENDED;
+ public static final String GROUP_TYPE_BATTERY_EXTENDED = GROUP_BATTERY + OPTION_EXTENDED;
+ public static final String GROUP_TYPE_PRESSURE_EXTENDED = GROUP_PRESSURE + OPTION_EXTENDED;
+ public static final String GROUP_TYPE_TEMPERATURE_EXTENDED = GROUP_TEMPERATURE + OPTION_EXTENDED;
+ public static final String GROUP_TYPE_AIR_QUALITY_EXTENDED = GROUP_AIR_QUALITY + OPTION_EXTENDED;
+ public static final String GROUP_TYPE_TEMPERATURE_OUTSIDE = GROUP_TEMPERATURE + OPTION_OUTSIDE;
+
+ // Channel ids
+ public static final String CHANNEL_VALUE = "value";
+ public static final String CHANNEL_TREND = "trend";
+ public static final String CHANNEL_MAX_TIME = "max-time";
+ public static final String CHANNEL_MIN_TIME = "min-time";
+ public static final String CHANNEL_MAX_VALUE = "max-today";
+ public static final String CHANNEL_MIN_VALUE = "min-today";
+ public static final String CHANNEL_HUMIDEX = "humidex";
+ public static final String CHANNEL_CO2 = "co2";
+ public static final String CHANNEL_HEALTH_INDEX = "health-index";
+ public static final String CHANNEL_HUMIDEX_SCALE = "humidex-scale";
+ public static final String CHANNEL_DEWPOINT = "dewpoint";
+ public static final String CHANNEL_DEWPOINT_DEP = "dewpoint-depression";
+ public static final String CHANNEL_HEAT_INDEX = "heat-index";
+ public static final String CHANNEL_ABSOLUTE_PRESSURE = "absolute";
+ public static final String CHANNEL_LAST_SEEN = "last-seen";
+ public static final String CHANNEL_MEASURES_TIMESTAMP = "measures";
+ public static final String CHANNEL_LOW_BATTERY = "low-battery";
+ public static final String CHANNEL_BATTERY_STATUS = "status";
+ public static final String CHANNEL_SIGNAL_STRENGTH = "strength";
+ public static final String CHANNEL_SUM_RAIN1 = "sum-1";
+ public static final String CHANNEL_SUM_RAIN24 = "sum-24";
+ public static final String CHANNEL_WIND_ANGLE = "angle";
+ public static final String CHANNEL_WIND_STRENGTH = "strength";
+ public static final String CHANNEL_MAX_WIND_STRENGTH = "max-strength";
+ public static final String CHANNEL_DATE_MAX_WIND_STRENGTH = "max-strength-date";
+ public static final String CHANNEL_GUST_ANGLE = "gust-angle";
+ public static final String CHANNEL_GUST_STRENGTH = "gust-strength";
+ public static final String CHANNEL_SETPOINT_MODE = "mode";
+ public static final String CHANNEL_SETPOINT_START_TIME = "start";
+ public static final String CHANNEL_SETPOINT_END_TIME = "end";
+ public static final String CHANNEL_THERM_RELAY = "relay-status";
+ public static final String CHANNEL_ANTICIPATING = "anticipating";
+ public static final String CHANNEL_ROOM_WINDOW_OPEN = "window-open";
+ public static final String CHANNEL_ROOM_HEATING_POWER = "heating-power-request";
+ public static final String CHANNEL_PLANNING = "planning";
+ public static final String CHANNEL_PERSON_COUNT = "person-count";
+ public static final String CHANNEL_UNKNOWN_PERSON_COUNT = "unknown-person-count";
+ public static final String CHANNEL_UNKNOWN_PERSON_PICTURE = "unknown-person-picture";
+ public static final String CHANNEL_MONITORING = "monitoring";
+ public static final String CHANNEL_SD_CARD = "sd-card";
+ public static final String CHANNEL_ALIM_STATUS = "alim";
+ public static final String CHANNEL_LIVEPICTURE = "picture";
+ public static final String CHANNEL_LIVEPICTURE_VPN_URL = "vpn-picture-url";
+ public static final String CHANNEL_LIVEPICTURE_LOCAL_URL = "local-picture-url";
+ public static final String CHANNEL_LIVESTREAM_VPN_URL = "vpn-stream-url";
+ public static final String CHANNEL_LIVESTREAM_LOCAL_URL = "local-stream-url";
+ public static final String CHANNEL_EVENT_TYPE = "type";
+ public static final String CHANNEL_EVENT_SUBTYPE = "subtype";
+ public static final String CHANNEL_EVENT_VIDEO_STATUS = "video-status";
+ public static final String CHANNEL_EVENT_MESSAGE = "message";
+ public static final String CHANNEL_EVENT_TIME = "time";
+ public static final String CHANNEL_EVENT_SNAPSHOT = "snapshot";
+ public static final String CHANNEL_EVENT_SNAPSHOT_URL = "snapshot-url";
+ public static final String CHANNEL_EVENT_VIDEO_VPN_URL = "vpn-video-url";
+ public static final String CHANNEL_EVENT_VIDEO_LOCAL_URL = "local-video-url";
+ public static final String CHANNEL_EVENT_PERSON_ID = "person-id";
+ public static final String CHANNEL_EVENT_CAMERA_ID = "camera-id";
+ public static final String CHANNEL_PERSON_AT_HOME = "at-home";
+ public static final String CHANNEL_PERSON_AVATAR = "avatar";
+ public static final String CHANNEL_PERSON_AVATAR_URL = "avatar-url";
+ public static final String CHANNEL_HOME_EVENT = "home-event";
+ public static final String CHANNEL_SETPOINT_DURATION = "setpoint-duration";
+ public static final String CHANNEL_FLOODLIGHT = "floodlight";
}
*/
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;
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.netatmo")
public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(NetatmoHandlerFactory.class);
- private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
- private final Map<ThingUID, ServiceRegistration<?>> webHookServiceRegs = new HashMap<>();
+
+ private final NetatmoDescriptionProvider stateDescriptionProvider;
+ private final HttpClient httpClient;
+ private final NADeserializer deserializer;
private final HttpService httpService;
- private final NATherm1StateDescriptionProvider stateDescriptionProvider;
- private final TimeZoneProvider timeZoneProvider;
- private final LocaleProvider localeProvider;
- private final TranslationProvider translationProvider;
- private boolean backgroundDiscovery;
+ private final BindingConfiguration configuration = new BindingConfiguration();
@Activate
- public NetatmoHandlerFactory(final @Reference HttpService httpService,
- final @Reference NATherm1StateDescriptionProvider stateDescriptionProvider,
- final @Reference TimeZoneProvider timeZoneProvider, final @Reference LocaleProvider localeProvider,
- final @Reference TranslationProvider translationProvider) {
- this.httpService = httpService;
+ public NetatmoHandlerFactory(@Reference NetatmoDescriptionProvider stateDescriptionProvider,
+ @Reference HttpClientFactory factory, @Reference NADeserializer deserializer,
+ @Reference HttpService httpService, Map<String, @Nullable Object> config) {
this.stateDescriptionProvider = stateDescriptionProvider;
- this.timeZoneProvider = timeZoneProvider;
- this.localeProvider = localeProvider;
- this.translationProvider = translationProvider;
+ this.httpClient = factory.getCommonHttpClient();
+ this.httpService = httpService;
+ this.deserializer = deserializer;
+ configChanged(config);
}
- @Override
- protected void activate(ComponentContext componentContext) {
- super.activate(componentContext);
- Dictionary<String, Object> properties = componentContext.getProperties();
- Object property = properties.get("backgroundDiscovery");
- if (property instanceof Boolean) {
- backgroundDiscovery = ((Boolean) property).booleanValue();
- } else {
- backgroundDiscovery = false;
+ @Modified
+ public void configChanged(Map<String, @Nullable Object> config) {
+ BindingConfiguration newConf = ConfigParser.configurationAs(config, BindingConfiguration.class);
+ if (newConf != null) {
+ configuration.update(newConf);
}
- logger.debug("backgroundDiscovery {}", backgroundDiscovery);
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
- return (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID));
+ return ModuleType.AS_SET.stream().anyMatch(mt -> mt.thingTypeUID.equals(thingTypeUID));
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
- if (thingTypeUID.equals(APIBRIDGE_THING_TYPE)) {
- WelcomeWebHookServlet servlet = registerWebHookServlet(thing.getUID());
- NetatmoBridgeHandler bridgeHandler = new NetatmoBridgeHandler((Bridge) thing, servlet);
- registerDeviceDiscoveryService(bridgeHandler);
- return bridgeHandler;
- } else if (thingTypeUID.equals(MODULE1_THING_TYPE)) {
- return new NAModule1Handler(thing, timeZoneProvider);
- } else if (thingTypeUID.equals(MODULE2_THING_TYPE)) {
- return new NAModule2Handler(thing, timeZoneProvider);
- } else if (thingTypeUID.equals(MODULE3_THING_TYPE)) {
- return new NAModule3Handler(thing, timeZoneProvider);
- } else if (thingTypeUID.equals(MODULE4_THING_TYPE)) {
- return new NAModule4Handler(thing, timeZoneProvider);
- } else if (thingTypeUID.equals(MAIN_THING_TYPE)) {
- return new NAMainHandler(thing, timeZoneProvider);
- } else if (thingTypeUID.equals(HOMECOACH_THING_TYPE)) {
- return new NAHealthyHomeCoachHandler(thing, timeZoneProvider);
- } else if (thingTypeUID.equals(PLUG_THING_TYPE)) {
- return new NAPlugHandler(thing, timeZoneProvider);
- } else if (thingTypeUID.equals(THERM1_THING_TYPE)) {
- return new NATherm1Handler(thing, stateDescriptionProvider, timeZoneProvider);
- } else if (thingTypeUID.equals(WELCOME_HOME_THING_TYPE)) {
- return new NAWelcomeHomeHandler(thing, timeZoneProvider);
- } else if (thingTypeUID.equals(WELCOME_CAMERA_THING_TYPE)) {
- return new NAWelcomeCameraHandler(thing, timeZoneProvider);
- } else if (thingTypeUID.equals(PRESENCE_CAMERA_THING_TYPE)) {
- return new NAPresenceCameraHandler(thing, timeZoneProvider);
- } else if (thingTypeUID.equals(WELCOME_PERSON_THING_TYPE)) {
- return new NAWelcomePersonHandler(thing, timeZoneProvider);
- } else {
- logger.warn("ThingHandler not found for {}", thing.getThingTypeUID());
- return null;
- }
+ return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst()
+ .map(mt -> buildHandler(thing, mt)).orElse(null);
}
- @Override
- protected void removeHandler(ThingHandler thingHandler) {
- if (thingHandler instanceof NetatmoBridgeHandler) {
- ThingUID thingUID = thingHandler.getThing().getUID();
- unregisterDeviceDiscoveryService(thingUID);
- unregisterWebHookServlet(thingUID);
+ private BaseThingHandler buildHandler(Thing thing, ModuleType moduleType) {
+ if (ModuleType.ACCOUNT.equals(moduleType)) {
+ return new ApiBridgeHandler((Bridge) thing, httpClient, httpService, deserializer, configuration);
}
- }
+ CommonInterface handler = moduleType.isABridge() ? new DeviceHandler((Bridge) thing) : new ModuleHandler(thing);
- private synchronized void registerDeviceDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler) {
- if (bundleContext != null) {
- NetatmoModuleDiscoveryService discoveryService = new NetatmoModuleDiscoveryService(netatmoBridgeHandler,
- localeProvider, translationProvider);
- Map<String, Object> configProperties = new HashMap<>();
- configProperties.put(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY,
- Boolean.valueOf(backgroundDiscovery));
- discoveryService.activate(configProperties);
- discoveryServiceRegs.put(netatmoBridgeHandler.getThing().getUID(), bundleContext
- .registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
- }
- }
-
- private synchronized void unregisterDeviceDiscoveryService(ThingUID thingUID) {
- ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(thingUID);
- if (serviceReg != null) {
- NetatmoModuleDiscoveryService service = (NetatmoModuleDiscoveryService) bundleContext
- .getService(serviceReg.getReference());
- serviceReg.unregister();
- if (service != null) {
- service.deactivate();
+ List<ChannelHelper> helpers = new ArrayList<>();
+ moduleType.channelHelpers.forEach(helperClass -> {
+ try {
+ helpers.add(helperClass.getConstructor().newInstance());
+ } catch (ReflectiveOperationException e) {
+ logger.warn("Error creating or initializing helper class : {}", e.getMessage());
}
- }
- }
+ });
- private synchronized @Nullable WelcomeWebHookServlet registerWebHookServlet(ThingUID thingUID) {
- WelcomeWebHookServlet servlet = null;
- if (bundleContext != null) {
- servlet = new WelcomeWebHookServlet(httpService, thingUID.getId());
- webHookServiceRegs.put(thingUID,
- bundleContext.registerService(HttpServlet.class.getName(), servlet, new Hashtable<>()));
- }
- return servlet;
- }
+ moduleType.capabilities.forEach(capability -> {
+ Capability newCap = null;
+ if (capability == DeviceCapability.class) {
+ newCap = new DeviceCapability(handler);
+ } else if (capability == AirCareCapability.class) {
+ newCap = new AirCareCapability(handler);
+ } else if (capability == EventCapability.class) {
+ newCap = new EventCapability(handler);
+ } else if (capability == HomeCapability.class) {
+ newCap = new HomeCapability(handler, stateDescriptionProvider);
+ } else if (capability == WeatherCapability.class) {
+ newCap = new WeatherCapability(handler);
+ } else if (capability == RoomCapability.class) {
+ newCap = new RoomCapability(handler);
+ } else if (capability == PersonCapability.class) {
+ newCap = new PersonCapability(handler, stateDescriptionProvider, helpers);
+ } else if (capability == CameraCapability.class) {
+ newCap = new CameraCapability(handler, stateDescriptionProvider, helpers);
+ } else if (capability == PresenceCapability.class) {
+ newCap = new PresenceCapability(handler, stateDescriptionProvider, helpers);
+ } else if (capability == MeasureCapability.class) {
+ newCap = new MeasureCapability(handler, helpers);
+ } else if (capability == ChannelHelperCapability.class) {
+ newCap = new ChannelHelperCapability(handler, helpers);
+ }
+ if (newCap != null) {
+ handler.getCapabilities().put(newCap);
+ } else {
+ logger.warn("No factory entry defined to create Capability : {}", capability);
+ }
+ });
- private synchronized void unregisterWebHookServlet(ThingUID thingUID) {
- ServiceRegistration<?> serviceReg = webHookServiceRegs.remove(thingUID);
- if (serviceReg != null) {
- serviceReg.unregister();
- }
+ return (BaseThingHandler) handler;
}
}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 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;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * This class holds various unit/measurement conversion methods
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - updated heat index
- */
-@NonNullByDefault
-public class WeatherUtils {
-
- /**
- * Calculate the heat index using temperature and humidity
- * https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
- *
- * @param temperature in (°C)
- * @param humidity relative level (%)
- * @return heatIndex in (°C)
- */
- public static double getHeatIndex(double temperature, double humidity) {
- double tempF = (temperature * 9.0 / 5.0) + 32.0; // calculations are done in Fahrenheit
- double heatIndex;
- if (tempF >= 80.0) {
- heatIndex = -42.379 + (2.04901523 * tempF) + (10.14333127 * humidity) - (0.22475541 * tempF * humidity)
- - (0.00683783 * tempF * tempF) - (0.05481717 * humidity * humidity)
- + (0.00122874 * tempF * tempF * humidity) + (0.00085282 * tempF * humidity * humidity)
- - (0.00000199 * tempF * tempF * humidity * humidity);
- if (humidity < 13.0 && tempF <= 112.0) {
- heatIndex -= ((13.0 - humidity) / 4.0) * Math.sqrt((17.0 - Math.abs(tempF - 95.0)) / 17.0);
- } else if (humidity > 85.0 && tempF <= 87.0) {
- heatIndex += ((humidity - 85.0) / 10.0) * ((87.0 - tempF) / 5.0);
- }
- } else {
- heatIndex = 0.5 * (tempF + 61.0 + ((tempF - 68.0) * 1.2) + (humidity * 0.094));
- }
-
- return (heatIndex - 32) * 5.0 / 9.0; // convert back to Celsius
- }
-
- public static double getDewPointDep(double temperature, double dewpoint) {
- return temperature - dewpoint;
- }
-
- /**
- * Compute the Dewpoint temperature given temperature and hygrometry
- * valid up to 60 degrees, from
- * http://en.wikipedia.org/wiki/Dew_point#Calculating_the_dew_point
- *
- * @param temperature in (°C)
- * @param humidity relative level (%)
- * @return dewpoint temperature
- */
- public static double getDewPoint(double temperature, double humidity) {
- double a = 17.271, b = 237.2;
- double gamma = ((a * temperature) / (b + temperature)) + Math.log(humidity / 100.0);
- return b * gamma / (a - gamma);
- }
-
- /**
- * Compute the Humidex index given temperature and hygrometry
- *
- *
- * @param temperature in (°C)
- * @param hygro relative level (%)
- * @return Humidex index value
- */
- public static double getHumidex(double temperature, double hygro) {
- double result = 6.112 * Math.pow(10, 7.5 * temperature / (237.7 + temperature)) * hygro / 100;
- result = temperature + 0.555555556 * (result - 10);
- return result;
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.action;
+
+import java.util.Optional;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.capability.EnergyCapability;
+import org.openhab.core.automation.annotation.ActionInput;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.openhab.core.thing.binding.ThingActions;
+import org.openhab.core.thing.binding.ThingActionsScope;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link RoomActions} defines thing actions for RoomHandler.
+ *
+ * @author Markus Dillmann - Initial contribution
+ */
+@ThingActionsScope(name = "netatmo")
+@NonNullByDefault
+public class RoomActions implements ThingActions {
+ private final Logger logger = LoggerFactory.getLogger(RoomActions.class);
+ private static final Set<SetpointMode> ALLOWED_MODES = Set.of(SetpointMode.MAX, SetpointMode.MANUAL,
+ SetpointMode.HOME);
+
+ private @Nullable CommonInterface handler;
+ private Optional<EnergyCapability> energy = Optional.empty();
+
+ public RoomActions() {
+ logger.debug("Netatmo RoomActions service created");
+ }
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof CommonInterface) {
+ CommonInterface commonHandler = (CommonInterface) handler;
+ this.handler = commonHandler;
+ energy = commonHandler.getHomeCapability(EnergyCapability.class);
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return (ThingHandler) handler;
+ }
+
+ /**
+ * The setThermpoint room thing action
+ */
+ @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
+ public void setThermpoint(
+ @ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp,
+ @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) {
+ setThermpoint(temp, endTime, "MANUAL");
+ }
+
+ @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
+ public void seThermpoint(
+ @ActionInput(name = "mode", label = "@text/actionInputModeLabel", description = "@text/actionInputModeDesc") @Nullable String mode,
+ @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) {
+ setThermpoint(null, endTime, mode);
+ }
+
+ @RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
+ public void setThermpoint(
+ @ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp,
+ @ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime,
+ @ActionInput(name = "mode", label = "@text/actionInputModeLabel", description = "@text/actionInputModeDesc") @Nullable String mode) {
+ CommonInterface roomHandler = handler;
+ if (roomHandler != null) {
+ String roomId = roomHandler.getId();
+ SetpointMode targetMode = SetpointMode.UNKNOWN;
+ Long targetEndTime = endTime;
+ Double targetTemp = temp;
+ if (mode != null) {
+ try {
+ targetMode = SetpointMode.valueOf(mode);
+ if (!ALLOWED_MODES.contains(targetMode)) {
+ logger.info("Mode can only be MAX, HOME or MANUAL for a room");
+ return;
+ }
+ } catch (IllegalArgumentException e) {
+ logger.info("Invalid mode passed : {} - {}", mode, e.getMessage());
+ return;
+ }
+ }
+ if (temp != null) {
+ logger.debug("Temperature provided, mode forced to MANUAL.");
+ targetMode = SetpointMode.MANUAL;
+ if (targetEndTime == null) {
+ logger.info("Temperature provided but no endtime given, action ignored");
+ return;
+ }
+ } else {
+ if (SetpointMode.HOME.equals(targetMode)) {
+ targetEndTime = 0L;
+ targetTemp = 0.0;
+ } else {
+ logger.info("mode is required if no temperature setpoint provided");
+ return;
+ }
+ }
+
+ try {
+ double setpointTemp = targetTemp != null ? targetTemp : 0;
+ long setpointEnd = targetEndTime;
+ SetpointMode setpointMode = targetMode;
+ energy.ifPresent(cap -> cap.setRoomThermTemp(roomId, setpointTemp, setpointEnd, setpointMode));
+ } catch (IllegalArgumentException e) {
+ logger.debug("Ignoring setRoomThermpoint command due to illegal argument exception: {}",
+ e.getMessage());
+ }
+ } else {
+ logger.info("Handler not set for room thing actions.");
+ }
+ }
+
+ /**
+ * Static setThermpoint method for Rules DSL backward compatibility
+ */
+ public static void setThermpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime,
+ @Nullable String mode) {
+ ((RoomActions) actions).setThermpoint(temp, endTime, mode);
+ }
+
+ public static void setThermpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime) {
+ setThermpoint(actions, temp, endTime, null);
+ }
+
+ public static void setThermpoint(ThingActions actions, @Nullable String mode, @Nullable Long endTime) {
+ setThermpoint(actions, null, endTime, mode);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain.StationDataResponse;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * Base class for all Air Care related endpoints
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class AircareApi extends RestManager {
+
+ public AircareApi(ApiBridgeHandler apiClient) {
+ super(apiClient, FeatureArea.AIR_CARE);
+ }
+
+ /**
+ * Returns data from Healthy Home Coach Station (measures and device specific data).
+ *
+ * @param deviceId Id of the device you want to retrieve information of (optional)
+ * @return StationDataResponse
+ * @throws NetatmoException If fail to call the API, e.g. server error or deserializing
+ */
+ public StationDataResponse getHomeCoachData(@Nullable String deviceId) throws NetatmoException {
+ UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMECOACH, PARAM_DEVICEID, deviceId);
+ return get(uriBuilder, StationDataResponse.class);
+ }
+
+ public NAMain getHomeCoach(String deviceId) throws NetatmoException {
+ ListBodyResponse<NAMain> answer = getHomeCoachData(deviceId).getBody();
+ if (answer != null) {
+ NAMain station = answer.getElement(deviceId);
+ if (station != null) {
+ return station;
+ }
+ }
+ throw new NetatmoException("Unexpected answer querying device '%s' : not found.", deviceId);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link ApiResponse} models a response returned by API call
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ApiResponse<T> {
+ /**
+ * The {@link Ok} models a response that only holds the result of the request sent to the API
+ */
+ static class Ok extends ApiResponse<String> {
+ private static final String SUCCESS = "ok";
+
+ boolean failed() {
+ return !SUCCESS.equals(getStatus());
+ }
+ }
+
+ private String status = "";
+ private @Nullable T body;
+
+ public String getStatus() {
+ return status;
+ }
+
+ public @Nullable T getBody() {
+ return body;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.PATH_OAUTH;
+import static org.openhab.core.auth.oauth2client.internal.Keyword.*;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
+import org.openhab.binding.netatmo.internal.api.dto.AccessTokenResponse;
+import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration.Credentials;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AuthenticationApi} handles oAuth2 authentication and token refreshing
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class AuthenticationApi extends RestManager {
+ private static final URI OAUTH_URI = getApiBaseBuilder().path(PATH_OAUTH).build();
+
+ private final ScheduledExecutorService scheduler;
+ private final Logger logger = LoggerFactory.getLogger(AuthenticationApi.class);
+
+ private @Nullable ScheduledFuture<?> refreshTokenJob;
+ private Optional<AccessTokenResponse> tokenResponse = Optional.empty();
+ private String scope = "";
+
+ public AuthenticationApi(ApiBridgeHandler bridge, ScheduledExecutorService scheduler) {
+ super(bridge, FeatureArea.NONE);
+ this.scheduler = scheduler;
+ }
+
+ public void authenticate(Credentials credentials, Set<FeatureArea> features) throws NetatmoException {
+ Set<FeatureArea> requestedFeatures = !features.isEmpty() ? features : FeatureArea.AS_SET;
+ scope = FeatureArea.toScopeString(requestedFeatures);
+ requestToken(credentials.clientId, credentials.clientSecret,
+ Map.of(USERNAME, credentials.username, PASSWORD, credentials.password, SCOPE, scope));
+ }
+
+ private void requestToken(String id, String secret, Map<String, String> entries) throws NetatmoException {
+ Map<String, String> payload = new HashMap<>(entries);
+ payload.putAll(Map.of(GRANT_TYPE, entries.keySet().contains(PASSWORD) ? PASSWORD : REFRESH_TOKEN, CLIENT_ID, id,
+ CLIENT_SECRET, secret));
+ disconnect();
+ AccessTokenResponse response = post(OAUTH_URI, AccessTokenResponse.class, payload);
+ refreshTokenJob = scheduler.schedule(() -> {
+ try {
+ requestToken(id, secret, Map.of(REFRESH_TOKEN, response.getRefreshToken()));
+ } catch (NetatmoException e) {
+ logger.warn("Unable to refresh access token : {}", e.getMessage());
+ }
+ }, Math.round(response.getExpiresIn() * 0.8), TimeUnit.SECONDS);
+ tokenResponse = Optional.of(response);
+ }
+
+ public void disconnect() {
+ tokenResponse = Optional.empty();
+ }
+
+ public void dispose() {
+ ScheduledFuture<?> job = refreshTokenJob;
+ if (job != null) {
+ job.cancel(true);
+ }
+ refreshTokenJob = null;
+ }
+
+ public @Nullable String getAuthorization() {
+ return tokenResponse.map(at -> String.format("Bearer %s", at.getAccessToken())).orElse(null);
+ }
+
+ public boolean matchesScopes(Set<Scope> requiredScopes) {
+ // either we do not require any scope, either connected and all scopes available
+ return requiredScopes.isEmpty()
+ || (isConnected() && tokenResponse.map(at -> at.getScope().containsAll(requiredScopes)).orElse(false));
+ }
+
+ public boolean isConnected() {
+ return !tokenResponse.isEmpty();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link BodyResponse} models a response returned by API call containing
+ * a list of elements.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class BodyResponse<T extends NAObject> {
+ @SerializedName(value = "home")
+ private @Nullable T element;
+
+ public @Nullable T getElement() {
+ return element;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import java.util.Collection;
+import java.util.Set;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.NAHomeStatusResponse;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * The {@link HomeApi} handles general API endpoints not requiring specific scope area
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeApi extends RestManager {
+
+ public HomeApi(ApiBridgeHandler apiClient) {
+ super(apiClient, FeatureArea.NONE);
+ }
+
+ public @Nullable HomeStatus getHomeStatus(String homeId) throws NetatmoException {
+ UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMESTATUS, PARAM_HOMEID, homeId);
+
+ NAHomeStatusResponse response = get(uriBuilder, NAHomeStatusResponse.class);
+ NAHomeStatus body = response.getBody();
+ return body != null ? body.getHomeStatus().orElse(null) : null;
+ }
+
+ public @Nullable HomeData getHomeData(String homeId) throws NetatmoException {
+ Collection<HomeData> result = getHomesData(homeId, null);
+ return result.isEmpty() ? null : result.iterator().next();
+ }
+
+ public Collection<HomeData> getHomesData(@Nullable String homeId, @Nullable ModuleType type)
+ throws NetatmoException {
+ UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_HOMES_DATA, PARAM_HOMEID, homeId);
+
+ if (type != null) {
+ uriBuilder.queryParam(PARAM_GATEWAYTYPE, type.name());
+ }
+
+ HomeData.HomesDataResponse response = get(uriBuilder, HomeData.HomesDataResponse.class);
+ ListBodyResponse<HomeData> body = response.getBody();
+ return body != null ? body.getElements() : Set.of();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api;
+
+import java.util.Collection;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link ListBodyResponse} models a response returned by API call containing
+ * a list of elements.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ListBodyResponse<T extends NAObject> {
+ @SerializedName(value = "devices", alternate = { "homes", "events_list", "events" })
+ private NAObjectMap<T> elements = new NAObjectMap<>();
+
+ @Nullable
+ T getElement(String id) {
+ return elements.get(id);
+ }
+
+ public Collection<T> getElements() {
+ return elements.values();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api;
+
+import static org.eclipse.jetty.http.HttpMethod.POST;
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * Base class for all various rest managers
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public abstract class RestManager {
+ private static final UriBuilder API_BASE_BUILDER = UriBuilder.fromUri(URL_API);
+ private static final UriBuilder APP_URI_BUILDER = UriBuilder.fromUri(URL_APP).path(PATH_API);
+ private static final UriBuilder API_URI_BUILDER = getApiBaseBuilder().path(PATH_API);
+
+ private final Set<Scope> requiredScopes;
+ private final ApiBridgeHandler apiBridge;
+
+ public RestManager(ApiBridgeHandler apiBridge, FeatureArea features) {
+ this.requiredScopes = features.scopes;
+ this.apiBridge = apiBridge;
+ }
+
+ protected <T extends ApiResponse<?>> T get(UriBuilder uriBuilder, Class<T> clazz) throws NetatmoException {
+ return executeUri(uriBuilder, HttpMethod.GET, clazz, null, null);
+ }
+
+ protected <T extends ApiResponse<?>> T post(UriBuilder uriBuilder, Class<T> clazz, @Nullable String payload,
+ @Nullable String contentType) throws NetatmoException {
+ return executeUri(uriBuilder, HttpMethod.POST, clazz, payload, contentType);
+ }
+
+ protected <T> T post(URI uri, Class<T> clazz, Map<String, String> entries) throws NetatmoException {
+ return apiBridge.executeUri(uri, POST, clazz, toRequest(entries),
+ "application/x-www-form-urlencoded;charset=UTF-8", 3);
+ }
+
+ private <T extends ApiResponse<?>> T executeUri(UriBuilder uriBuilder, HttpMethod method, Class<T> clazz,
+ @Nullable String payload, @Nullable String contentType) throws NetatmoException {
+ URI uri = uriBuilder.build();
+ T response = apiBridge.executeUri(uri, method, clazz, payload, contentType, 3);
+ if (response instanceof ApiResponse.Ok && ((ApiResponse.Ok) response).failed()) {
+ throw new NetatmoException("Command failed : %s for uri : %s", response.getStatus(), uri.toString());
+ }
+ return response;
+ }
+
+ private static UriBuilder appendParams(UriBuilder builder, @Nullable Object... params) {
+ if (params.length % 2 != 0) {
+ throw new IllegalArgumentException("appendParams : params count must be even");
+ }
+ for (int i = 0; i < params.length; i += 2) {
+ Object query = params[i];
+ if (query instanceof String) {
+ Object param = params[i + 1];
+ if (param != null) {
+ builder.queryParam((String) query, param);
+ }
+ } else {
+ throw new IllegalArgumentException("appendParams : even parameters must be Strings");
+ }
+ }
+ return builder;
+ }
+
+ protected static UriBuilder getApiBaseBuilder() {
+ return API_BASE_BUILDER.clone();
+ }
+
+ public static UriBuilder getApiUriBuilder(String path, @Nullable Object... params) {
+ return appendParams(API_URI_BUILDER.clone().path(path), params);
+ }
+
+ protected static UriBuilder getAppUriBuilder(String path, @Nullable Object... params) {
+ return appendParams(APP_URI_BUILDER.clone().path(path), params);
+ }
+
+ private String toRequest(Map<String, String> entries) {
+ return entries.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
+ }
+
+ public Set<Scope> getRequiredScopes() {
+ return requiredScopes;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
+import org.openhab.binding.netatmo.internal.api.dto.Home;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent.NAEventsDataResponse;
+import org.openhab.binding.netatmo.internal.api.dto.Ping;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * Base class for all Security related endpoints
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class SecurityApi extends RestManager {
+ public SecurityApi(ApiBridgeHandler apiClient) {
+ super(apiClient, FeatureArea.SECURITY);
+ }
+
+ /**
+ * Dissociates a webhook from a user.
+ *
+ * @throws NetatmoException If fail to call the API, e.g. server error or deserializing
+ */
+ public void dropWebhook() throws NetatmoException {
+ UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_DROPWEBHOOK);
+ post(uriBuilder, ApiResponse.Ok.class, null, null);
+ }
+
+ /**
+ * Links a callback url to a user.
+ *
+ * @param uri Your webhook callback url (required)
+ * @throws NetatmoException If fail to call the API, e.g. server error or deserializing
+ */
+ public void addwebhook(URI uri) throws NetatmoException {
+ UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_ADDWEBHOOK, PARAM_URL, uri.toString());
+ post(uriBuilder, ApiResponse.Ok.class, null, null);
+ }
+
+ public Collection<HomeEvent> getPersonEvents(String homeId, String personId) throws NetatmoException {
+ UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETEVENTS, PARAM_HOMEID, homeId, PARAM_PERSONID, personId,
+ PARAM_OFFSET, 1);
+ NAEventsDataResponse response = get(uriBuilder, NAEventsDataResponse.class);
+ BodyResponse<Home> body = response.getBody();
+ if (body != null) {
+ Home home = body.getElement();
+ if (home != null) {
+ return home.getEvents().stream().filter(event -> personId.equals(event.getPersonId()))
+ .collect(Collectors.toList());
+ }
+ }
+ throw new NetatmoException("home should not be null");
+ }
+
+ public Collection<HomeEvent> getCameraEvents(String homeId, String cameraId) throws NetatmoException {
+ UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETEVENTS, PARAM_HOMEID, homeId, PARAM_DEVICEID, cameraId);
+ NAEventsDataResponse response = get(uriBuilder, NAEventsDataResponse.class);
+ BodyResponse<Home> body = response.getBody();
+ if (body != null) {
+ Home home = body.getElement();
+ if (home != null) {
+ return home.getEvents();
+ }
+ }
+ throw new NetatmoException("home should not be null");
+ }
+
+ public String ping(String vpnUrl) throws NetatmoException {
+ UriBuilder uriBuilder = UriBuilder.fromUri(vpnUrl).path(PATH_COMMAND).path(SUB_PATH_PING);
+ Ping response = get(uriBuilder, Ping.class);
+ return response.getStatus();
+ }
+
+ public void changeStatus(String localCameraURL, boolean setOn) throws NetatmoException {
+ UriBuilder uriBuilder = UriBuilder.fromUri(localCameraURL).path(PATH_COMMAND).path(SUB_PATH_CHANGESTATUS);
+ uriBuilder.queryParam(PARAM_STATUS, setOn ? "on" : "off");
+ post(uriBuilder, ApiResponse.Ok.class, null, null);
+ }
+
+ public void changeFloodLightMode(String localCameraURL, FloodLightMode mode) throws NetatmoException {
+ UriBuilder uriBuilder = UriBuilder.fromUri(localCameraURL).path(PATH_COMMAND).path(SUB_PATH_FLOODLIGHTSET);
+ uriBuilder.queryParam("config", "%7B%22mode%22:%22" + mode.toString() + "%22%7D");
+ get(uriBuilder, ApiResponse.Ok.class);
+ }
+
+ public void setPersonAwayStatus(String homeId, String personId, boolean away) throws NetatmoException {
+ UriBuilder uriBuilder = getAppUriBuilder(away ? SUB_PATH_PERSON_AWAY : SUB_PATH_PERSON_HOME);
+ String payload = String.format(
+ away ? "{\"home_id\":\"%s\",\"person_id\":\"%s\"}" : "{\"home_id\":\"%s\",\"person_ids\":[\"%s\"]}",
+ homeId, personId);
+ post(uriBuilder, ApiResponse.Ok.class, payload, "application/json;charset=utf-8");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api;
+
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.dto.MeasureBodyElem;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain.StationDataResponse;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+
+/**
+ * Base class for all Weather related endpoints
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class WeatherApi extends RestManager {
+ private class NAMeasuresResponse extends ApiResponse<List<MeasureBodyElem<Double>>> {
+ }
+
+ private class NADateMeasuresResponse extends ApiResponse<List<MeasureBodyElem<ZonedDateTime>>> {
+ }
+
+ public WeatherApi(ApiBridgeHandler apiClient) {
+ super(apiClient, FeatureArea.WEATHER);
+ }
+
+ /**
+ *
+ * Returns data from a user's Weather Stations (measures and device specific data);
+ *
+ * @param deviceId Id of the device you want to retrieve information of (optional)
+ * @param getFavorites Whether to include the user's favorite Weather Stations in addition to the user's
+ * own Weather Stations (optional, default to false)
+ * @return StationDataResponse
+ * @throws NetatmoException If fail to call the API, e.g. server error or deserializing
+ */
+ public StationDataResponse getStationsData(@Nullable String deviceId, boolean getFavorites)
+ throws NetatmoException {
+ UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETSTATION, PARAM_DEVICEID, deviceId, //
+ PARAM_FAVORITES, getFavorites);
+ StationDataResponse response = get(uriBuilder, StationDataResponse.class);
+ return response;
+ }
+
+ public NAMain getStationData(String deviceId) throws NetatmoException {
+ ListBodyResponse<NAMain> answer = getStationsData(deviceId, true).getBody();
+ if (answer != null) {
+ NAMain station = answer.getElement(deviceId);
+ if (station != null) {
+ return station;
+ }
+ }
+ throw new NetatmoException("Unexpected answer searching device '%s' : not found.", deviceId);
+ }
+
+ public @Nullable Object getMeasures(String deviceId, @Nullable String moduleId, @Nullable String scale,
+ String apiDescriptor) throws NetatmoException {
+ MeasureBodyElem<?> result = getMeasure(deviceId, moduleId, scale, apiDescriptor);
+ return result.getSingleValue();
+ }
+
+ public @Nullable Object getMeasures(String deviceId, @Nullable String moduleId, @Nullable String scale,
+ String apiDescriptor, String limit) throws NetatmoException {
+ String queryLimit = limit;
+ if (!apiDescriptor.contains("_")) {
+ queryLimit += "_" + apiDescriptor;
+ }
+
+ MeasureBodyElem<?> result = getMeasure(deviceId, moduleId, scale, queryLimit.toLowerCase());
+ return result.getSingleValue();
+ }
+
+ private MeasureBodyElem<?> getMeasure(String deviceId, @Nullable String moduleId, @Nullable String scale,
+ String measureType) throws NetatmoException {
+ // NAMeasuresResponse is not designed for optimize=false
+ UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GETMEASURE, PARAM_DEVICEID, deviceId, "real_time", true,
+ "date_end", "last", "optimize", true, "type", measureType.toLowerCase(), PARAM_MODULEID, moduleId);
+
+ if (scale != null) {
+ uriBuilder.queryParam("scale", scale.toLowerCase());
+ }
+ if (measureType.startsWith("date")) {
+ NADateMeasuresResponse response = get(uriBuilder, NADateMeasuresResponse.class);
+ List<MeasureBodyElem<ZonedDateTime>> body = response.getBody();
+ if (body != null && !body.isEmpty()) {
+ return body.get(0);
+ }
+ } else {
+ NAMeasuresResponse response = get(uriBuilder, NAMeasuresResponse.class);
+ List<MeasureBodyElem<Double>> body = response.getBody();
+ if (body != null && !body.isEmpty()) {
+ return body.get(0);
+ }
+ }
+ throw new NetatmoException("Empty response while getting measurements");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.data;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This enum describes sub events in relation to a given event
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public enum EventSubType {
+ SD_CARD_MISSING(List.of(EventType.SD), 1),
+ SD_CARD_INSERTED(List.of(EventType.SD), 2),
+ SD_CARD_FORMATTED(List.of(EventType.SD), 3),
+ SD_CARD_WORKING(List.of(EventType.SD), 4),
+ SD_CARD_DEFECTIVE(List.of(EventType.SD), 5),
+ SD_CARD_INCOMPATIBLE_SPEED(List.of(EventType.SD), 6),
+ SD_CARD_INSUFFICIENT_SPACE(List.of(EventType.SD), 7),
+ ALIM_INCORRECT_POWER(List.of(EventType.ALIM), 1),
+ ALIM_CORRECT_POWER(List.of(EventType.ALIM), 2),
+
+ // Artificially implemented by the binding subtypes
+ PERSON_ARRIVAL(List.of(EventType.PERSON, EventType.PERSON_HOME), 1),
+ PERSON_SEEN(List.of(EventType.PERSON), 2),
+ PERSON_DEPARTURE(List.of(EventType.PERSON_AWAY), 1),
+ MOVEMENT_HUMAN(List.of(EventType.MOVEMENT, EventType.HUMAN), 1),
+ MOVEMENT_VEHICLE(List.of(EventType.MOVEMENT), 2),
+ MOVEMENT_ANIMAL(List.of(EventType.MOVEMENT, EventType.ANIMAL), 3);
+
+ public final List<EventType> types;
+ public final int subType;
+
+ EventSubType(List<EventType> types, int i) {
+ this.types = types;
+ this.subType = i;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.data;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This enum describes events generated by webhooks and the type of
+ * module they are related to according to API documentation
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public enum EventType {
+ UNKNOWN(),
+
+ @SerializedName("person") // When the Indoor Camera detects a face
+ PERSON(ModuleType.PERSON, ModuleType.WELCOME),
+
+ @SerializedName("person_away") // When geofencing indicates that the person has left the home
+ PERSON_AWAY(ModuleType.PERSON, ModuleType.HOME),
+
+ @SerializedName("person_home") // When the person is declared at home
+ PERSON_HOME(ModuleType.PERSON, ModuleType.HOME),
+
+ @SerializedName("outdoor") // When the Outdoor Camera detects a human, a car or an animal
+ OUTDOOR(ModuleType.PRESENCE, ModuleType.DOORBELL),
+
+ @SerializedName("daily_summary") // When the Outdoor Camera video summary of the last 24 hours is available
+ DAILY_SUMMARY(ModuleType.PRESENCE),
+
+ @SerializedName("movement") // When the Indoor Camera detects motion
+ MOVEMENT(ModuleType.WELCOME),
+
+ @SerializedName("human") // When the Indoor Camera detects human motion
+ HUMAN(ModuleType.WELCOME),
+
+ @SerializedName("animal") // When the Indoor Camera detects animal motion
+ ANIMAL(ModuleType.WELCOME),
+
+ @SerializedName("new_module") // A new Module has been paired with the Indoor Camera
+ NEW_MODULE(ModuleType.WELCOME),
+
+ @SerializedName("module_connect") // Module is connected with the Indoor Camera
+ MODULE_CONNECT(ModuleType.WELCOME),
+
+ @SerializedName("module_disconnect") // Module lost its connection with the Indoor Camera
+ MODULE_DISCONNECT(ModuleType.WELCOME),
+
+ @SerializedName("module_low_battery") // Module's battery is low
+ MODULE_LOW_BATTERY(ModuleType.WELCOME),
+
+ @SerializedName("module_end_update") // Module's firmware update is over
+ MODULE_END_UPDATE(ModuleType.WELCOME),
+
+ @SerializedName("connection") // When the Camera connects to Netatmo servers
+ CONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+ @SerializedName("disconnection") // When the Camera loses connection with Netatmo servers
+ DISCONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+ @SerializedName("on") // When Camera Monitoring is resumed
+ ON(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+ @SerializedName("off") // When Camera Monitoring is turned off
+ OFF(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+ @SerializedName("boot") // When the Camera is booting
+ BOOT(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+ @SerializedName("sd") // When Camera SD Card status changes
+ SD(ModuleType.WELCOME, ModuleType.PRESENCE),
+
+ @SerializedName("alim") // When Camera power supply status changes
+ ALIM(ModuleType.WELCOME, ModuleType.PRESENCE);
+
+ private final Set<ModuleType> appliesTo;
+
+ EventType(ModuleType... appliesTo) {
+ this.appliesTo = Set.of(appliesTo);
+ }
+
+ @Override
+ public String toString() {
+ return name().toLowerCase();
+ }
+
+ public boolean appliesOn(ModuleType searched) {
+ return appliesTo.contains(searched);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.data;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID;
+import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*;
+
+import java.net.URI;
+import java.util.EnumSet;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.handler.capability.AirCareCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.CameraCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.Capability;
+import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.RoomCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityExtChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.BatteryChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.BatteryExtChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.EventChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.EventPersonChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.HomeEnergyChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.HomeSecurityChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.HumidityChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.LocationChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.MeasuresChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.NoiseChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.PersonChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.PresenceChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.PressureChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.PressureExtChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.RainChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.RoomChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.SetpointChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.SignalChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureExtChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureOutChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.Therm1ChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampExtChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.WindChannelHelper;
+import org.openhab.core.thing.ThingTypeUID;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This enum all handled Netatmo modules and devices along with their capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public enum ModuleType {
+ UNKNOWN(FeatureArea.NONE, null, null, List.of(), List.of()),
+ ACCOUNT(FeatureArea.NONE, null, null, List.of(), List.of()),
+ @SerializedName("NAHome")
+ HOME(FeatureArea.NONE, "NAHome", ACCOUNT,
+ List.of(DeviceCapability.class, EventCapability.class, HomeCapability.class, ChannelHelperCapability.class),
+ List.of(HomeSecurityChannelHelper.class, HomeEnergyChannelHelper.class)),
+ @SerializedName("NAPerson")
+ PERSON(FeatureArea.SECURITY, "NAPerson", HOME,
+ List.of(EventCapability.class, PersonCapability.class, ChannelHelperCapability.class),
+ List.of(PersonChannelHelper.class, EventPersonChannelHelper.class)),
+ @SerializedName("NACamera")
+ WELCOME(FeatureArea.SECURITY, "NACamera", HOME,
+ List.of(EventCapability.class, CameraCapability.class, ChannelHelperCapability.class),
+ List.of(CameraChannelHelper.class, SignalChannelHelper.class, EventChannelHelper.class)),
+ @SerializedName("NOC")
+ PRESENCE(FeatureArea.SECURITY, "NOC", HOME,
+ List.of(EventCapability.class, PresenceCapability.class, ChannelHelperCapability.class),
+ List.of(CameraChannelHelper.class, PresenceChannelHelper.class, SignalChannelHelper.class,
+ EventChannelHelper.class)),
+ @SerializedName("NIS")
+ SIREN(FeatureArea.SECURITY, "NIS", HOME, List.of(ChannelHelperCapability.class),
+ List.of(BatteryChannelHelper.class, TimestampChannelHelper.class, SignalChannelHelper.class)),
+ @SerializedName("NDB")
+ DOORBELL(FeatureArea.SECURITY, "NDB", HOME, List.of(ChannelHelperCapability.class),
+ List.of(SignalChannelHelper.class)),
+ @SerializedName("NAMain")
+ WEATHER_STATION(FeatureArea.WEATHER, "NAMain", ACCOUNT,
+ List.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class,
+ ChannelHelperCapability.class),
+ List.of(PressureExtChannelHelper.class, NoiseChannelHelper.class, HumidityChannelHelper.class,
+ TemperatureExtChannelHelper.class, AirQualityChannelHelper.class, LocationChannelHelper.class,
+ TimestampExtChannelHelper.class, MeasuresChannelHelper.class, SignalChannelHelper.class)),
+ @SerializedName("NAModule1")
+ OUTDOOR(FeatureArea.WEATHER, "NAModule1", WEATHER_STATION,
+ List.of(MeasureCapability.class, ChannelHelperCapability.class),
+ List.of(HumidityChannelHelper.class, TemperatureOutChannelHelper.class, BatteryChannelHelper.class,
+ MeasuresChannelHelper.class, TimestampExtChannelHelper.class, SignalChannelHelper.class)),
+ @SerializedName("NAModule2")
+ WIND(FeatureArea.WEATHER, "NAModule2", WEATHER_STATION, List.of(ChannelHelperCapability.class),
+ List.of(WindChannelHelper.class, BatteryChannelHelper.class, TimestampExtChannelHelper.class,
+ SignalChannelHelper.class)),
+ @SerializedName("NAModule3")
+ RAIN(FeatureArea.WEATHER, "NAModule3", WEATHER_STATION,
+ List.of(MeasureCapability.class, ChannelHelperCapability.class),
+ List.of(RainChannelHelper.class, BatteryChannelHelper.class, MeasuresChannelHelper.class,
+ TimestampExtChannelHelper.class, SignalChannelHelper.class)),
+ @SerializedName("NAModule4")
+ INDOOR(FeatureArea.WEATHER, "NAModule4", WEATHER_STATION,
+ List.of(MeasureCapability.class, ChannelHelperCapability.class),
+ List.of(HumidityChannelHelper.class, TemperatureExtChannelHelper.class, AirQualityChannelHelper.class,
+ BatteryChannelHelper.class, MeasuresChannelHelper.class, TimestampExtChannelHelper.class,
+ SignalChannelHelper.class)),
+ @SerializedName("NHC")
+ HOME_COACH(FeatureArea.AIR_CARE, "NHC", ACCOUNT,
+ List.of(DeviceCapability.class, AirCareCapability.class, MeasureCapability.class,
+ ChannelHelperCapability.class),
+ List.of(NoiseChannelHelper.class, HumidityChannelHelper.class, AirQualityExtChannelHelper.class,
+ TemperatureChannelHelper.class, PressureChannelHelper.class, TimestampExtChannelHelper.class,
+ SignalChannelHelper.class, MeasuresChannelHelper.class, LocationChannelHelper.class)),
+ @SerializedName("NAPlug")
+ PLUG(FeatureArea.ENERGY, "NAPlug", HOME, List.of(ChannelHelperCapability.class),
+ List.of(SignalChannelHelper.class)),
+ @SerializedName("NATherm1")
+ THERMOSTAT(FeatureArea.ENERGY, "NATherm1", HOME, List.of(ChannelHelperCapability.class),
+ List.of(Therm1ChannelHelper.class, BatteryExtChannelHelper.class, SignalChannelHelper.class)),
+ @SerializedName("NARoom")
+ ROOM(FeatureArea.ENERGY, "NARoom", HOME, List.of(RoomCapability.class, ChannelHelperCapability.class),
+ List.of(RoomChannelHelper.class, SetpointChannelHelper.class)),
+ @SerializedName("NRV")
+ VALVE(FeatureArea.ENERGY, "NRV", HOME, List.of(ChannelHelperCapability.class),
+ List.of(BatteryExtChannelHelper.class, SignalChannelHelper.class));
+
+ public static final EnumSet<ModuleType> AS_SET = EnumSet.allOf(ModuleType.class);
+
+ private final @Nullable ModuleType bridgeType;
+ public final List<String> groupTypes = new LinkedList<>();
+ public final List<String> extensions = new LinkedList<>();
+ public final List<Class<? extends ChannelHelper>> channelHelpers;
+ public final List<Class<? extends Capability>> capabilities;
+ public final ThingTypeUID thingTypeUID;
+ public final FeatureArea feature;
+ public final @Nullable String apiName;
+
+ ModuleType(FeatureArea feature, @Nullable String apiName, @Nullable ModuleType bridge,
+ List<Class<? extends Capability>> capabilities, List<Class<? extends ChannelHelper>> helpers) {
+ this.channelHelpers = helpers;
+ this.bridgeType = bridge;
+ this.feature = feature;
+ this.capabilities = capabilities;
+ this.apiName = apiName;
+ thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase().replace("_", "-"));
+ try {
+ for (Class<? extends ChannelHelper> helperClass : helpers) {
+ ChannelHelper helper = helperClass.getConstructor().newInstance();
+ groupTypes.addAll(helper.getChannelGroupTypes());
+ extensions.addAll(helper.getExtensibleChannels());
+ }
+ } catch (RuntimeException | ReflectiveOperationException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public boolean isLogical() {
+ return !channelHelpers.contains(SignalChannelHelper.class);
+ }
+
+ public boolean isABridge() {
+ for (ModuleType mt : ModuleType.values()) {
+ if (this.equals(mt.bridgeType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int[] getSignalLevels() {
+ if (!isLogical()) {
+ return (channelHelpers.contains(BatteryChannelHelper.class)
+ || channelHelpers.contains(BatteryExtChannelHelper.class)) ? RADIO_SIGNAL_LEVELS
+ : WIFI_SIGNAL_LEVELS;
+ }
+ throw new IllegalArgumentException(
+ "This should not be called for module type : " + name() + ", please file a bug report.");
+ }
+
+ public ModuleType getBridge() {
+ ModuleType bridge = bridgeType;
+ return bridge != null ? bridge : ModuleType.UNKNOWN;
+ }
+
+ public URI getConfigDescription() {
+ return URI.create(BINDING_ID + ":"
+ + (equals(ACCOUNT) ? "api_bridge"
+ : equals(HOME) ? "home"
+ : (isLogical() ? "virtual"
+ : ModuleType.UNKNOWN.equals(getBridge()) ? "configurable" : "device")));
+ }
+
+ public static ModuleType from(ThingTypeUID thingTypeUID) {
+ return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst()
+ .orElseThrow(() -> new IllegalArgumentException());
+ }
+
+ public static ModuleType from(String apiName) {
+ return ModuleType.AS_SET.stream().filter(mt -> apiName.equals(mt.apiName)).findFirst()
+ .orElseThrow(() -> new IllegalArgumentException());
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.data;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.core.library.CoreItemFactory.*;
+import static org.openhab.core.library.unit.MetricPrefix.*;
+
+import java.net.URI;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.types.StateDescriptionFragment;
+import org.openhab.core.types.StateDescriptionFragmentBuilder;
+import org.openhab.core.types.util.UnitUtils;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class holds various definitions and settings provided by the Netatmo
+ * API documentation
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NetatmoConstants {
+ public static class Measure {
+ public final double minValue;
+ public final double maxValue;
+ public final int scale;
+ public final Unit<?> unit;
+
+ private Measure(double minValue, double maxValue, double precision, Unit<?> unit) {
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.unit = unit;
+ String[] splitter = Double.valueOf(precision).toString().split("\\.");
+ if (splitter.length > 1) {
+ int dec = Integer.parseInt(splitter[1]);
+ this.scale = dec > 0 ? Integer.toString(dec).length() : 0;
+ } else {
+ this.scale = 0;
+ }
+ }
+ }
+
+ public static class MeasureChannelDetails {
+ private static final StateDescriptionFragmentBuilder BUILDER = StateDescriptionFragmentBuilder.create();
+ public final URI configURI;
+ public final String itemType;
+ public final StateDescriptionFragment stateDescriptionFragment;
+
+ private MeasureChannelDetails(String measureType, String itemType, String pattern) {
+ this.configURI = URI.create(String.join(":", BINDING_ID, measureType, "config"));
+ this.itemType = itemType;
+ this.stateDescriptionFragment = BUILDER.withReadOnly(true).withPattern(pattern).build();
+ }
+ }
+
+ public enum MeasureClass {
+ INSIDE_TEMPERATURE(0, 50, 0.3, SIUnits.CELSIUS, "temp", "measure", true),
+ OUTSIDE_TEMPERATURE(-40, 65, 0.3, SIUnits.CELSIUS, "temp", "measure", true),
+ HEAT_INDEX(-40, 65, 1, SIUnits.CELSIUS, "", "", false),
+ PRESSURE(260, 1260, 1, HECTO(SIUnits.PASCAL), "pressure", "measure", true),
+ CO2(0, 5000, 50, Units.PARTS_PER_MILLION, "co2", "measure", true),
+ NOISE(35, 120, 1, Units.DECIBEL, "noise", "measure", true),
+ RAIN_QUANTITY(0, 150, 0.1, MILLI(SIUnits.METRE), "sum_rain", "sum_rain", false),
+ RAIN_INTENSITY(0, 150, 0.1, Units.MILLIMETRE_PER_HOUR, "", "", false),
+ WIND_SPEED(0, 160, 1.8, SIUnits.KILOMETRE_PER_HOUR, "", "", false),
+ WIND_ANGLE(0, 360, 5, Units.DEGREE_ANGLE, "", "", false),
+ HUMIDITY(0, 100, 3, Units.PERCENT, "hum", "measure", true);
+
+ public static final EnumSet<MeasureClass> AS_SET = EnumSet.allOf(MeasureClass.class);
+
+ public final Measure measureDefinition;
+ public final String apiDescriptor;
+ public final Map<String, MeasureChannelDetails> channels = new HashMap<>(2);
+
+ MeasureClass(double min, double max, double precision, Unit<?> unit, String apiDescriptor, String confFragment,
+ boolean canScale) {
+ this.measureDefinition = new Measure(min, max, precision, unit);
+ this.apiDescriptor = apiDescriptor;
+ if (!apiDescriptor.isBlank()) {
+ String dimension = UnitUtils.getDimensionName(unit);
+
+ channels.put(String.join("-", apiDescriptor, "measurement"),
+ new MeasureChannelDetails(confFragment, String.join(":", NUMBER, dimension),
+ String.format("%%.%df %s", measureDefinition.scale, UnitUtils.UNIT_PLACEHOLDER)));
+ if (canScale) {
+ channels.put(String.join("-", apiDescriptor, GROUP_TIMESTAMP),
+ new MeasureChannelDetails(GROUP_TIMESTAMP, DATETIME, "%1$tA, %1$td.%1$tm. %1$tH:%1$tM"));
+ }
+ }
+ }
+ }
+
+ // Netatmo API urls
+ public static final String URL_API = "https://api.netatmo.com/";
+ public static final String URL_APP = "https://app.netatmo.net/";
+ public static final String PATH_OAUTH = "oauth2/token";
+ public static final String PATH_API = "api";
+ public static final String PATH_COMMAND = "command";
+ public static final String SUB_PATH_PERSON_AWAY = "setpersonsaway";
+ public static final String SUB_PATH_PERSON_HOME = "setpersonshome";
+ public static final String SUB_PATH_HOMES_DATA = "homesdata";
+ public static final String SUB_PATH_ADDWEBHOOK = "addwebhook";
+ public static final String SUB_PATH_DROPWEBHOOK = "dropwebhook";
+ public static final String SUB_PATH_SETROOMTHERMPOINT = "setroomthermpoint";
+ public static final String SUB_PATH_SETTHERMMODE = "setthermmode";
+ public static final String SUB_PATH_SWITCHSCHEDULE = "switchschedule";
+ public static final String SUB_PATH_GETSTATION = "getstationsdata";
+ public static final String SUB_PATH_GETMEASURE = "getmeasure";
+ public static final String SUB_PATH_HOMESTATUS = "homestatus";
+ public static final String SUB_PATH_HOMECOACH = "gethomecoachsdata";
+ public static final String SUB_PATH_GETEVENTS = "getevents";
+ public static final String SUB_PATH_PING = "ping";
+ public static final String SUB_PATH_CHANGESTATUS = "changestatus";
+ public static final String SUB_PATH_FLOODLIGHTSET = "floodlight_set_config";
+ public static final String PARAM_DEVICEID = "device_id";
+ public static final String PARAM_MODULEID = "module_id";
+ public static final String PARAM_HOMEID = "home_id";
+ public static final String PARAM_ROOMID = "room_id";
+ public static final String PARAM_PERSONID = "person_id";
+ public static final String PARAM_SCHEDULEID = "schedule_id";
+ public static final String PARAM_OFFSET = "offset";
+ public static final String PARAM_GATEWAYTYPE = "gateway_types";
+ public static final String PARAM_MODE = "mode";
+ public static final String PARAM_URL = "url";
+ public static final String PARAM_FAVORITES = "get_favorites";
+ public static final String PARAM_STATUS = "status";
+
+ // Global variables
+ public static final int THERM_MAX_SETPOINT = 30;
+
+ // Token scopes
+ public static enum Scope {
+ @SerializedName("read_station")
+ READ_STATION,
+ @SerializedName("read_thermostat")
+ READ_THERMOSTAT,
+ @SerializedName("write_thermostat")
+ WRITE_THERMOSTAT,
+ @SerializedName("read_camera")
+ READ_CAMERA,
+ @SerializedName("write_camera")
+ WRITE_CAMERA,
+ @SerializedName("access_camera")
+ ACCESS_CAMERA,
+ @SerializedName("read_presence")
+ READ_PRESENCE,
+ @SerializedName("write_presence")
+ WRITE_PRESENCE,
+ @SerializedName("access_presence")
+ ACCESS_PRESENCE,
+ @SerializedName("read_smokedetector")
+ READ_SMOKEDETECTOR,
+ @SerializedName("read_homecoach")
+ READ_HOMECOACH,
+ @SerializedName("read_doorbell")
+ READ_DOORBELL,
+ @SerializedName("write_doorbell")
+ WRITE_DOORBELL,
+ @SerializedName("access_doorbell")
+ ACCESS_DOORBELL,
+ UNKNOWN;
+ }
+
+ private static final Set<Scope> SMOKE = Set.of(Scope.READ_SMOKEDETECTOR);
+ private static final Set<Scope> WELCOME = Set.of(Scope.READ_CAMERA, Scope.WRITE_CAMERA, Scope.ACCESS_CAMERA);
+ private static final Set<Scope> DOORBELL = Set.of(Scope.READ_DOORBELL, Scope.WRITE_DOORBELL, Scope.ACCESS_DOORBELL);
+ private static final Set<Scope> PRESENCE = Set.of(Scope.READ_PRESENCE, Scope.WRITE_PRESENCE, Scope.ACCESS_PRESENCE);
+
+ // Radio signal quality thresholds
+ static final int[] WIFI_SIGNAL_LEVELS = new int[] { 99, 84, 69, 54 }; // Resp : bad, average, good, full
+ static final int[] RADIO_SIGNAL_LEVELS = new int[] { 90, 80, 70, 60 }; // Resp : low, medium, high, full
+
+ public static enum FeatureArea {
+ AIR_CARE(Scope.READ_HOMECOACH),
+ WEATHER(Scope.READ_STATION),
+ ENERGY(Scope.READ_THERMOSTAT, Scope.WRITE_THERMOSTAT),
+ SECURITY(Stream.of(WELCOME, PRESENCE, SMOKE, DOORBELL).flatMap(Set::stream).toArray(Scope[]::new)),
+ NONE();
+
+ public static final Set<FeatureArea> AS_SET = EnumSet.allOf(FeatureArea.class);
+
+ public static String toScopeString(Set<FeatureArea> featureSet) {
+ return featureSet.stream().map(fa -> fa.scopes).flatMap(Set::stream).map(s -> s.name().toLowerCase())
+ .collect(Collectors.joining(" "));
+ }
+
+ public final Set<Scope> scopes;
+
+ FeatureArea(Scope... scopes) {
+ this.scopes = Set.of(scopes);
+ }
+ }
+
+ // Thermostat definitions
+ public static enum SetpointMode {
+ @SerializedName("program")
+ PROGRAM("program"),
+ @SerializedName("away")
+ AWAY("away"),
+ @SerializedName("hg")
+ FROST_GUARD("hg"),
+ @SerializedName("manual")
+ MANUAL("manual"),
+ @SerializedName("off")
+ OFF("off"),
+ @SerializedName("max")
+ MAX("max"),
+ @SerializedName("schedule")
+ SCHEDULE("schedule"),
+ HOME("home"),
+ UNKNOWN("");
+
+ public final String apiDescriptor;
+
+ SetpointMode(String descriptor) {
+ this.apiDescriptor = descriptor;
+ }
+ }
+
+ public static enum ThermostatZoneType {
+ @SerializedName("0")
+ DAY("0"),
+ @SerializedName("1")
+ NIGHT("1"),
+ @SerializedName("2")
+ AWAY("2"),
+ @SerializedName("3")
+ FROST_GUARD("3"),
+ @SerializedName("4")
+ CUSTOM("4"),
+ @SerializedName("5")
+ ECO("5"),
+ @SerializedName("8")
+ COMFORT("8"),
+ UNKNOWN("");
+
+ public final String zoneId;
+
+ private ThermostatZoneType(String id) {
+ zoneId = id;
+ }
+ }
+
+ public enum FloodLightMode {
+ @SerializedName("on")
+ ON,
+ @SerializedName("off")
+ OFF,
+ @SerializedName("auto")
+ AUTO,
+ UNKNOWN;
+ }
+
+ public enum EventCategory {
+ @SerializedName("human")
+ HUMAN,
+ @SerializedName("animal")
+ ANIMAL,
+ @SerializedName("vehicle")
+ VEHICLE,
+ UNKNOWN;
+ }
+
+ public enum TrendDescription {
+ @SerializedName("up")
+ UP,
+ @SerializedName("stable")
+ STABLE,
+ @SerializedName("down")
+ DOWN,
+ UNKNOWN;
+ }
+
+ public enum VideoStatus {
+ @SerializedName("recording")
+ RECORDING,
+ @SerializedName("available")
+ AVAILABLE,
+ @SerializedName("deleted")
+ DELETED,
+ UNKNOWN;
+ }
+
+ public enum SdCardStatus {
+ @SerializedName("1")
+ SD_CARD_MISSING,
+ @SerializedName("2")
+ SD_CARD_INSERTED,
+ @SerializedName("3")
+ SD_CARD_FORMATTED,
+ @SerializedName("4")
+ SD_CARD_WORKING,
+ @SerializedName("5")
+ SD_CARD_DEFECTIVE,
+ @SerializedName("6")
+ SD_CARD_INCOMPATIBLE_SPEED,
+ @SerializedName("7")
+ SD_CARD_INSUFFICIENT_SPACE,
+ UNKNOWN;
+ }
+
+ public enum AlimentationStatus {
+ @SerializedName("1")
+ ALIM_INCORRECT_POWER,
+ @SerializedName("2")
+ ALIM_CORRECT_POWER,
+ UNKNOWN;
+ }
+
+ public enum BatteryState {
+ @SerializedName("full")
+ FULL(100),
+ @SerializedName("high")
+ HIGH(80),
+ @SerializedName("medium")
+ MEDIUM(50),
+ @SerializedName("low")
+ LOW(15),
+ UNKNOWN(-1);
+
+ public final int level;
+
+ BatteryState(int i) {
+ this.level = i;
+ }
+ }
+
+ public enum ServiceError {
+ @SerializedName("99")
+ UNKNOWN,
+ @SerializedName("-2")
+ UNKNOWN_ERROR_IN_OAUTH,
+ @SerializedName("-1")
+ GRANT_IS_INVALID,
+ @SerializedName("1")
+ ACCESS_TOKEN_MISSING,
+ @SerializedName("2")
+ INVALID_TOKEN_MISSING,
+ @SerializedName("3")
+ ACCESS_TOKEN_EXPIRED,
+ @SerializedName("5")
+ APPLICATION_DEACTIVATED,
+ @SerializedName("7")
+ NOTHING_TO_MODIFY,
+ @SerializedName("9")
+ DEVICE_NOT_FOUND,
+ @SerializedName("10")
+ MISSING_ARGUMENTS,
+ @SerializedName("13")
+ OPERATION_FORBIDDEN,
+ @SerializedName("19")
+ IP_NOT_FOUND,
+ @SerializedName("21")
+ INVALID_ARGUMENT,
+ @SerializedName("22")
+ APPLICATION_NOT_FOUND,
+ @SerializedName("23")
+ USER_NOT_FOUND,
+ @SerializedName("25")
+ INVALID_DATE,
+ @SerializedName("26")
+ MAXIMUM_USAGE_REACHED,
+ @SerializedName("30")
+ INVALID_REFRESH_TOKEN,
+ @SerializedName("31")
+ METHOD_NOT_FOUND,
+ @SerializedName("35")
+ UNABLE_TO_EXECUTE,
+ @SerializedName("36")
+ PROHIBITED_STRING,
+ @SerializedName("37")
+ NO_MORE_SPACE_AVAILABLE_ON_THE_CAMERA,
+ @SerializedName("40")
+ JSON_GIVEN_HAS_AN_INVALID_ENCODING,
+ @SerializedName("41")
+ DEVICE_IS_UNREACHABLE;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.List;
+
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
+
+/**
+ * This is the Access Token Response, a simple value-object holding the result of an Access Token Request, as
+ * provided by Netatmo API.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public final class AccessTokenResponse {
+
+ /**
+ * The access token issued by the authorization server. It is used
+ * by the client to gain access to a resource.
+ *
+ */
+ private String accessToken;
+
+ /**
+ * Number of seconds that this OAuthToken is valid for since the time it was created.
+ *
+ */
+ private long expiresIn;
+
+ /**
+ * Refresh token is a string representing the authorization granted to
+ * the client by the resource owner. Unlike access tokens, refresh tokens are
+ * intended for use only with authorization servers and are never sent
+ * to resource servers.
+ *
+ */
+ private String refreshToken;
+
+ private List<Scope> scope;
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public long getExpiresIn() {
+ return expiresIn;
+ }
+
+ public String getRefreshToken() {
+ return refreshToken;
+ }
+
+ public List<Scope> getScope() {
+ return scope;
+ }
+
+ @Override
+ public String toString() {
+ return "AccessTokenResponse [accessToken=" + accessToken + ", expiresIn=" + expiresIn + ", refreshToken="
+ + refreshToken + ", scope=" + scope + "]";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+
+/**
+ * The {@link Device} holds common data for all Netatmo devices.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class Device extends NAThing {
+ private @Nullable NAObjectMap<Module> modules;
+ private long dateSetup;
+ private long lastUpgrade;
+ private @Nullable Place place;
+
+ public NAObjectMap<Module> getModules() {
+ NAObjectMap<Module> localModules = modules;
+ return localModules != null ? localModules : new NAObjectMap<>();
+ }
+
+ public long getDateSetup() {
+ return dateSetup;
+ }
+
+ public long getLastUpgrade() {
+ return lastUpgrade;
+ }
+
+ public Optional<Place> getPlace() {
+ return Optional.ofNullable(place);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.EventSubType;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link Event} holds information transferred by the webhook.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public abstract class Event extends NAObject {
+ protected EventType type = EventType.UNKNOWN;
+ @SerializedName(value = "camera_id", alternate = { "module_id" })
+ private String cameraId = "";
+ protected int subType = -1;
+
+ public abstract ZonedDateTime getTime();
+
+ public abstract @Nullable String getSnapshotUrl();
+
+ public abstract @Nullable String getPersonId();
+
+ public EventType getEventType() {
+ return type;
+ }
+
+ public String getCameraId() {
+ return cameraId;
+ }
+
+ @Override
+ public @Nullable String getName() {
+ String localMessage = super.getName();
+ return (localMessage != null ? localMessage.replace("<b>", "").replace("</b>", "") : "");
+ }
+
+ public Optional<EventSubType> getSubTypeDescription() {
+ return Stream.of(EventSubType.values()).filter(v -> v.types.contains(getEventType()) && v.subType == subType)
+ .findFirst();
+ }
+
+ public void setEventType(EventType type) {
+ this.type = type;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+/**
+ * The {@link Home} holds home information.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class Home extends Device implements Location {
+ private double[] coordinates = {};
+ private double altitude;
+ private List<HomeEvent> events = List.of();
+
+ @Override
+ public ModuleType getType() {
+ return ModuleType.HOME;
+ }
+
+ @Override
+ public double getAltitude() {
+ return altitude;
+ }
+
+ @Override
+ public double[] getCoordinates() {
+ return coordinates;
+ }
+
+ public List<HomeEvent> getEvents() {
+ return events;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.ApiResponse;
+import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+
+/**
+ * The {@link HomeData} holds home information returned by homesdata endpoint.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeData extends NAThing implements NAModule, LocationEx {
+ public class HomesDataResponse extends ApiResponse<ListBodyResponse<HomeData>> {
+ }
+
+ private double altitude;
+ private double[] coordinates = {};
+ private @Nullable String country;
+ private @Nullable String timezone;
+
+ private @Nullable String temperatureControlMode;
+ private SetpointMode thermMode = SetpointMode.UNKNOWN;
+ private int thermSetpointDefaultDuration;
+ private List<ThermProgram> schedules = List.of();
+
+ private NAObjectMap<HomeDataPerson> persons = new NAObjectMap<>();
+ private NAObjectMap<HomeDataRoom> rooms = new NAObjectMap<>();
+ private NAObjectMap<HomeDataModule> modules = new NAObjectMap<>();
+
+ @Override
+ public ModuleType getType() {
+ return ModuleType.HOME;
+ }
+
+ @Override
+ public double getAltitude() {
+ return altitude;
+ }
+
+ @Override
+ public double[] getCoordinates() {
+ return coordinates;
+ }
+
+ @Override
+ public Optional<String> getCountry() {
+ return Optional.ofNullable(country);
+ }
+
+ @Override
+ public Optional<String> getTimezone() {
+ return Optional.ofNullable(timezone);
+ }
+
+ public int getThermSetpointDefaultDuration() {
+ return thermSetpointDefaultDuration;
+ }
+
+ public SetpointMode getThermMode() {
+ return thermMode;
+ }
+
+ public NAObjectMap<HomeDataPerson> getPersons() {
+ return persons;
+ }
+
+ public List<HomeDataPerson> getKnownPersons() {
+ return persons.values().stream().filter(HomeDataPerson::isKnown).collect(Collectors.toList());
+ }
+
+ public Optional<String> getTemperatureControlMode() {
+ return Optional.ofNullable(temperatureControlMode);
+ }
+
+ public NAObjectMap<HomeDataRoom> getRooms() {
+ return rooms;
+ }
+
+ public NAObjectMap<HomeDataModule> getModules() {
+ return modules;
+ }
+
+ public Set<FeatureArea> getFeatures() {
+ return getModules().values().stream().map(m -> m.getType().feature).collect(Collectors.toSet());
+ }
+
+ public List<ThermProgram> getThermSchedules() {
+ return schedules;
+ }
+
+ public @Nullable ThermProgram getActiveProgram() {
+ return schedules.stream().filter(ThermProgram::isSelected).findFirst().orElse(null);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link HomeDataModule} holds module informations returned by getHomeData endpoint
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeDataModule extends NAThing implements NAModule {
+ private @Nullable ZonedDateTime setupDate;
+ private @Nullable String applianceType;
+ private List<String> moduleBridged = List.of();
+
+ public @Nullable String getApplianceType() {
+ return applianceType;
+ }
+
+ public Optional<ZonedDateTime> getSetupDate() {
+ return Optional.ofNullable(setupDate);
+ }
+
+ public List<String> getModuleBridged() {
+ return moduleBridged;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+/**
+ * The {@link HomeDataPerson} provides Person informations returned by getHomeData endpoint
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeDataPerson extends NAThing implements NAModule {
+ private @Nullable String url;
+
+ @Override
+ public ModuleType getType() {
+ return ModuleType.PERSON;
+ }
+
+ public boolean isKnown() {
+ return description != null;
+ }
+
+ public Optional<String> getUrl() {
+ return Optional.ofNullable(url);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+/**
+ * The {@link HomeDataRoom} provides Room informations returned by getHomeData endpoint
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HomeDataRoom extends NAObject implements NAModule {
+ private List<String> moduleIds = List.of();
+
+ @Override
+ public ModuleType getType() {
+ // In json api answer type for NARoom is used with free strings like kitchen, living...
+ return ModuleType.ROOM;
+ }
+
+ public List<String> getModuleIds() {
+ return moduleIds;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.ApiResponse;
+import org.openhab.binding.netatmo.internal.api.BodyResponse;
+import org.openhab.binding.netatmo.internal.api.data.EventSubType;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.EventCategory;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.VideoStatus;
+
+/**
+ * The {@link HomeEvent} holds information transferred by the webhook about a home event.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeEvent extends Event {
+ public class NAEventsDataResponse extends ApiResponse<BodyResponse<Home>> {
+ }
+
+ private ZonedDateTime time = ZonedDateTime.now();
+ private @Nullable String personId;
+ private EventCategory category = EventCategory.UNKNOWN;
+ private @Nullable Snapshot snapshot;
+ private @Nullable String videoId;
+ private VideoStatus videoStatus = VideoStatus.UNKNOWN;
+ private boolean isArrival;
+
+ @Override
+ public ZonedDateTime getTime() {
+ return time;
+ }
+
+ @Override
+ public @Nullable String getPersonId() {
+ return personId;
+ }
+
+ public @Nullable String getVideoId() {
+ return videoId;
+ }
+
+ public VideoStatus getVideoStatus() {
+ return videoStatus;
+ }
+
+ @Override
+ public Optional<EventSubType> getSubTypeDescription() {
+ // Blend extra information provided by this kind of event in subcategories...
+ if (type == EventType.PERSON) {
+ subType = isArrival ? EventSubType.PERSON_ARRIVAL.subType : EventSubType.PERSON_SEEN.subType;
+ } else if (type == EventType.PERSON_HOME) {
+ subType = EventSubType.PERSON_ARRIVAL.subType;
+ } else if (type == EventType.PERSON_AWAY) {
+ subType = EventSubType.PERSON_DEPARTURE.subType;
+ } else if (type == EventType.HUMAN) {
+ subType = EventSubType.MOVEMENT_HUMAN.subType;
+ } else if (type == EventType.ANIMAL) {
+ subType = EventSubType.MOVEMENT_ANIMAL.subType;
+ } else {
+ if (category == EventCategory.ANIMAL) {
+ subType = EventSubType.MOVEMENT_ANIMAL.subType;
+ } else if (category == EventCategory.HUMAN) {
+ subType = EventSubType.MOVEMENT_HUMAN.subType;
+ } else if (category == EventCategory.VEHICLE) {
+ subType = EventSubType.MOVEMENT_VEHICLE.subType;
+ }
+ }
+ // ... and let ancestor do his work
+ return super.getSubTypeDescription();
+ }
+
+ @Override
+ public @Nullable String getSnapshotUrl() {
+ Snapshot localSnap = snapshot;
+ return localSnap != null ? localSnap.getUrl() : null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.AlimentationStatus;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.BatteryState;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SdCardStatus;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link HomeStatusModule} holds module informations returned by getHomeData endpoint
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class HomeStatusModule extends NAThing {
+ private @Nullable String firmwareName;
+ private @Nullable String wifiState;
+ private @Nullable String status;
+ private @Nullable OnOffType monitoring;
+ private FloodLightMode floodlight = FloodLightMode.UNKNOWN;
+ private SdCardStatus sdStatus = SdCardStatus.UNKNOWN;
+ private AlimentationStatus alimStatus = AlimentationStatus.UNKNOWN;
+ private @Nullable String sirenStatus;
+ private @Nullable String vpnUrl;
+ private boolean isLocal;
+ private BatteryState batteryState = BatteryState.UNKNOWN;
+ private int batteryLevel;
+
+ private @Nullable OpenClosedType boilerStatus;
+ private boolean boilerValveComfortBoost;
+
+ public State getBoilerStatus() {
+ OpenClosedType status = boilerStatus;
+ return status != null ? status : UnDefType.NULL;
+ }
+
+ public boolean getBoilerValveComfortBoost() {
+ return boilerValveComfortBoost;
+ }
+
+ public Optional<String> getFirmwareName() {
+ return Optional.ofNullable(firmwareName);
+ }
+
+ public Optional<String> getWifiState() {
+ return Optional.ofNullable(wifiState);
+ }
+
+ public Optional<String> getStatus() {
+ return Optional.ofNullable(status);
+ }
+
+ public State getMonitoring() {
+ OnOffType localStatus = monitoring;
+ return localStatus != null ? localStatus : UnDefType.NULL;
+ }
+
+ public FloodLightMode getFloodlight() {
+ return floodlight;
+ }
+
+ public SdCardStatus getSdStatus() {
+ return sdStatus;
+ }
+
+ public AlimentationStatus getAlimStatus() {
+ return alimStatus;
+ }
+
+ public Optional<String> getSirenStatus() {
+ return Optional.ofNullable(sirenStatus);
+ }
+
+ public @Nullable String getVpnUrl() {
+ return vpnUrl;
+ }
+
+ public boolean isLocal() {
+ return isLocal;
+ }
+
+ public BatteryState getBatteryState() {
+ return batteryState;
+ }
+
+ public int getBatteryLevel() {
+ return batteryLevel;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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()));
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link LocationEx} is the common interface for dto holding a extra location data
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public interface LocationEx extends Location {
+ public Optional<String> getCountry();
+
+ public Optional<String> getTimezone();
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link MeasureBodyElem} holds a list of values returned by getMeasure endpoint.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class MeasureBodyElem<T> {
+ private List<List<T>> value = List.of();
+
+ public List<List<T>> getValue() {
+ return value;
+ }
+
+ public @Nullable T getSingleValue() {
+ if (!value.isEmpty()) {
+ List<T> first = value.get(0);
+ if (!first.isEmpty()) {
+ return first.get(0);
+ }
+ }
+ return null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.ApiResponse;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+
+/**
+ * The {@link NAHomeStatus} holds data for a given home.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class NAHomeStatus {
+ public class NAHomeStatusResponse extends ApiResponse<NAHomeStatus> {
+ }
+
+ public class HomeStatus extends NAThing {
+ private @Nullable NAObjectMap<HomeStatusModule> modules;
+ private @Nullable NAObjectMap<HomeStatusPerson> persons;
+ private @Nullable NAObjectMap<Room> rooms;
+
+ public NAObjectMap<HomeStatusModule> getModules() {
+ NAObjectMap<HomeStatusModule> localModules = modules;
+ return localModules != null ? localModules : new NAObjectMap<>();
+ }
+
+ public NAObjectMap<HomeStatusPerson> getPersons() {
+ NAObjectMap<HomeStatusPerson> localPersons = persons;
+ return localPersons != null ? localPersons : new NAObjectMap<>();
+ }
+
+ public NAObjectMap<Room> getRooms() {
+ NAObjectMap<Room> localRooms = rooms;
+ return localRooms != null ? localRooms : new NAObjectMap<>();
+ }
+ }
+
+ private @Nullable HomeStatus home;
+
+ public Optional<HomeStatus> getHomeStatus() {
+ return Optional.ofNullable(home);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.ApiResponse;
+import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
+
+/**
+ * The {@link NAMain} defines a weather or nhc device.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class NAMain extends Device {
+ public class StationDataResponse extends ApiResponse<ListBodyResponse<NAMain>> {
+ }
+
+ private boolean readOnly;
+
+ /**
+ * true when the user was invited to (or has favorited) a station, false when the user owns it
+ *
+ * @return readOnly
+ **/
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ public boolean hasFreshData(int dataFreshnessLimit) {
+ // check by comparing data freshness
+ ZonedDateTime localLastSeen = lastSeen;
+ if (localLastSeen != null && !getType().isLogical()) {
+ return Duration.between(localLastSeen.toInstant(), Instant.now()).getSeconds() < dataFreshnessLimit;
+ }
+ return true;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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();
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link NAThing} is the base class for devices and modules.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class NAThing extends NAObject implements NAModule {
+ @SerializedName(value = "rf_status", alternate = { "wifi_status", "rf_strength", "wifi_strength" })
+ private int radioStatus = -1;
+ @SerializedName(value = "last_seen", alternate = { "last_therm_seen", "last_status_store", "last_plug_seen",
+ "last_message", "last_activity" })
+ protected @Nullable ZonedDateTime lastSeen;
+ @SerializedName(value = "firmware", alternate = { "firmware_revision" })
+ private @Nullable String firmware;
+ private @Nullable Boolean reachable;
+ private @Nullable Dashboard dashboardData;
+
+ private @Nullable String roomId;
+ private @Nullable String bridge;
+ private ModuleType type = ModuleType.UNKNOWN;
+
+ @Override
+ public ModuleType getType() {
+ return type;
+ }
+
+ public boolean isReachable() {
+ // This is not implemented on all devices/modules, so if absent we consider it is reachable
+ Boolean localReachable = this.reachable;
+ return localReachable != null ? localReachable : true;
+ }
+
+ public void setReachable(boolean reachable) {
+ this.reachable = reachable;
+ }
+
+ public @Nullable Dashboard getDashboardData() {
+ return dashboardData;
+ }
+
+ public @Nullable String getFirmware() {
+ return firmware;
+ }
+
+ public int getRadioStatus() {
+ return radioStatus;
+ }
+
+ public Optional<ZonedDateTime> getLastSeen() {
+ return Optional.ofNullable(lastSeen);
+ }
+
+ /**
+ * @return true if the equipment has no parent, meaning its a device.
+ */
+ public boolean isDevice() {
+ return bridge == null;
+ }
+
+ public @Nullable String getBridge() {
+ return bridge;
+ }
+
+ public @Nullable String getRoomId() {
+ return roomId;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.ApiResponse;
+
+/**
+ * The {@link Ping} hold url data for a camera module
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class Ping extends ApiResponse<String> {
+ private String localUrl = "";
+ private @Nullable String productName;
+
+ @Override
+ public String getStatus() {
+ return localUrl;
+ }
+
+ @Override
+ public @Nullable String getBody() {
+ return productName;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link Place} reports location information of a Netatmo system.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class Place implements LocationEx {
+ private @Nullable String city;
+ private @Nullable String country;
+ private @Nullable String timezone;
+ private double altitude;
+ private double[] location = {};
+
+ public Optional<String> getCity() {
+ return Optional.ofNullable(city);
+ }
+
+ @Override
+ public Optional<String> getCountry() {
+ return Optional.ofNullable(country);
+ }
+
+ @Override
+ public Optional<String> getTimezone() {
+ return Optional.ofNullable(timezone);
+ }
+
+ @Override
+ public double getAltitude() {
+ return altitude;
+ }
+
+ @Override
+ public double[] getCoordinates() {
+ return location;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+
+/**
+ * The {@link ThermProgram} holds setpoint scheduling information.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class ThermProgram extends NAObject {
+ private NAObjectMap<Zone> zones = new NAObjectMap<>();
+ private List<TimeTableItem> timetable = List.of();
+ private boolean selected;
+
+ public List<TimeTableItem> getTimetable() {
+ return timetable;
+ }
+
+ public boolean isSelected() {
+ return selected;
+ }
+
+ public @Nullable Zone getZone(String id) {
+ return zones.get(id);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.api.dto;
+
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.deserialization.NAPushType;
+
+/**
+ * The {@link WebhookEvent} is responsible to hold
+ * data given back by the Netatmo API when calling the webhook
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WebhookEvent extends Event {
+ private @NonNullByDefault({}) NAPushType pushType;
+ private String homeId = "";
+ private @Nullable String snapshotUrl;
+ private NAObjectMap<Person> persons = new NAObjectMap<>();
+ // Webhook does not provide the event generation time, so we'll use the event reception time
+ private ZonedDateTime time = ZonedDateTime.now();
+
+ public String getHomeId() {
+ return homeId;
+ }
+
+ public NAObjectMap<Person> getPersons() {
+ return persons;
+ }
+
+ @Override
+ public EventType getEventType() {
+ return pushType.getEvent();
+ }
+
+ @Override
+ public ZonedDateTime getTime() {
+ return time;
+ }
+
+ @Override
+ public @Nullable String getPersonId() {
+ return persons.size() > 0 ? persons.keySet().iterator().next() : null;
+ }
+
+ @Override
+ public @Nullable String getSnapshotUrl() {
+ return snapshotUrl;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 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);
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.camera;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.io.IOException;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.io.net.http.HttpUtil;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.swagger.client.model.NAWelcomeCamera;
-
-/**
- * {@link CameraHandler} is the class used to handle Camera Data
- *
- * @author Sven Strohschein - Initial contribution (partly moved code from NAWelcomeCameraHandler to introduce
- * inheritance, see NAWelcomeCameraHandler)
- *
- */
-@NonNullByDefault
-public abstract class CameraHandler extends NetatmoModuleHandler<NAWelcomeCamera> {
-
- private static final String PING_URL_PATH = "/command/ping";
- private static final String STATUS_CHANGE_URL_PATH = "/command/changestatus";
- private static final String LIVE_PICTURE = "/live/snapshot_720.jpg";
-
- private final Logger logger = LoggerFactory.getLogger(CameraHandler.class);
-
- private Optional<CameraAddress> cameraAddress;
-
- protected CameraHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- cameraAddress = Optional.empty();
- }
-
- @Override
- public void handleCommand(ChannelUID channelUID, Command command) {
- String channelId = channelUID.getId();
- switch (channelId) {
- case CHANNEL_CAMERA_STATUS:
- case CHANNEL_WELCOME_CAMERA_STATUS:
- if (command == OnOffType.ON) {
- switchVideoSurveillance(true);
- } else if (command == OnOffType.OFF) {
- switchVideoSurveillance(false);
- }
- break;
- }
- super.handleCommand(channelUID, command);
- }
-
- @Override
- protected void updateProperties(NAWelcomeCamera moduleData) {
- updateProperties(null, moduleData.getType());
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- switch (channelId) {
- case CHANNEL_CAMERA_STATUS:
- return getStatusState();
- case CHANNEL_CAMERA_SDSTATUS:
- return getSdStatusState();
- case CHANNEL_CAMERA_ALIMSTATUS:
- return getAlimStatusState();
- case CHANNEL_CAMERA_ISLOCAL:
- return getIsLocalState();
- case CHANNEL_CAMERA_LIVEPICTURE_URL:
- return getLivePictureURLState();
- case CHANNEL_CAMERA_LIVEPICTURE:
- return getLivePictureState();
- case CHANNEL_CAMERA_LIVESTREAM_URL:
- return getLiveStreamState();
- }
- return super.getNAThingProperty(channelId);
- }
-
- protected State getStatusState() {
- return getModule().map(m -> toOnOffType(m.getStatus())).orElse(UnDefType.UNDEF);
- }
-
- protected State getSdStatusState() {
- return getModule().map(m -> toOnOffType(m.getSdStatus())).orElse(UnDefType.UNDEF);
- }
-
- protected State getAlimStatusState() {
- return getModule().map(m -> toOnOffType(m.getAlimStatus())).orElse(UnDefType.UNDEF);
- }
-
- protected State getIsLocalState() {
- return getModule().map(m -> toOnOffType(m.isIsLocal())).orElse(UnDefType.UNDEF);
- }
-
- protected State getLivePictureURLState() {
- return getLivePictureURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
- }
-
- protected State getLivePictureState() {
- Optional<String> livePictureURL = getLivePictureURL();
- return livePictureURL.isPresent() ? toRawType(livePictureURL.get()) : UnDefType.UNDEF;
- }
-
- protected State getLiveStreamState() {
- return getLiveStreamURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
- }
-
- /**
- * Get the url for the live snapshot
- *
- * @return Url of the live snapshot
- */
- private Optional<String> getLivePictureURL() {
- return getVpnUrl().map(u -> u += LIVE_PICTURE);
- }
-
- /**
- * Get the url for the live stream depending wether local or not
- *
- * @return Url of the live stream
- */
- private Optional<String> getLiveStreamURL() {
- Optional<String> result = getVpnUrl();
- if (!result.isPresent()) {
- return Optional.empty();
- }
-
- StringBuilder resultStringBuilder = new StringBuilder(result.get());
- resultStringBuilder.append("/live/index");
- if (isLocal()) {
- resultStringBuilder.append("_local");
- }
- resultStringBuilder.append(".m3u8");
- return Optional.of(resultStringBuilder.toString());
- }
-
- private Optional<String> getVpnUrl() {
- return getModule().map(NAWelcomeCamera::getVpnUrl);
- }
-
- public Optional<String> getStreamURL(String videoId) {
- Optional<String> result = getVpnUrl();
- if (!result.isPresent()) {
- return Optional.empty();
- }
-
- StringBuilder resultStringBuilder = new StringBuilder(result.get());
- resultStringBuilder.append("/vod/");
- resultStringBuilder.append(videoId);
- resultStringBuilder.append("/index");
- if (isLocal()) {
- resultStringBuilder.append("_local");
- }
- resultStringBuilder.append(".m3u8");
- return Optional.of(resultStringBuilder.toString());
- }
-
- private boolean isLocal() {
- return getModule().map(NAWelcomeCamera::isIsLocal).orElse(false);
- }
-
- private void switchVideoSurveillance(boolean isOn) {
- Optional<String> localCameraURL = getLocalCameraURL();
- if (localCameraURL.isPresent()) {
- String url = localCameraURL.get() + STATUS_CHANGE_URL_PATH + "?status=";
- if (isOn) {
- url += "on";
- } else {
- url += "off";
- }
- executeGETRequest(url);
-
- invalidateParentCacheAndRefresh();
- }
- }
-
- protected Optional<String> getLocalCameraURL() {
- Optional<String> vpnURLOptional = getVpnUrl();
- Optional<CameraAddress> address = cameraAddress;
- if (vpnURLOptional.isPresent()) {
- final String vpnURL = vpnURLOptional.get();
-
- // The local address is (re-)requested when it wasn't already determined or when the vpn address was
- // changed.
- if (!address.isPresent() || address.get().isVpnURLChanged(vpnURL)) {
- Optional<JSONObject> json = executeGETRequestJSON(vpnURL + PING_URL_PATH);
- address = json.map(j -> j.optString("local_url", null))
- .map(localURL -> new CameraAddress(vpnURL, localURL));
- cameraAddress = address;
- }
- }
- return address.map(CameraAddress::getLocalURL);
- }
-
- private Optional<JSONObject> executeGETRequestJSON(String url) {
- try {
- return executeGETRequest(url).map(JSONObject::new);
- } catch (JSONException e) {
- logger.warn("Error on parsing the content as JSON!", e);
- }
- return Optional.empty();
- }
-
- protected Optional<String> executeGETRequest(String url) {
- try {
- String content = HttpUtil.executeUrl("GET", url, 5000);
- if (content != null && !content.isEmpty()) {
- return Optional.of(content);
- }
- } catch (IOException e) {
- logger.warn("Error on accessing local camera url!", e);
- }
- return Optional.empty();
- }
-
- @Override
- protected boolean isReachable() {
- Optional<NAWelcomeCamera> module = getModule();
- return module.isPresent() ? !"disconnected".equalsIgnoreCase(module.get().getStatus()) : false;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.channelhelper;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link BatteryHelper} handle specific behavior
- * of modules using batteries
- *
- * @author Gaël L'hopital - Initial contribution
- *
- */
-@NonNullByDefault
-public class BatteryHelper {
- private final Logger logger = LoggerFactory.getLogger(BatteryHelper.class);
- private int batteryLow;
-
- private @Nullable Object module;
-
- public BatteryHelper(String batteryLevels) {
- List<String> thresholds = Arrays.asList(batteryLevels.split(","));
- batteryLow = Integer.parseInt(thresholds.get(1));
- }
-
- public void setModule(Object module) {
- this.module = module;
- }
-
- public Optional<State> getNAThingProperty(String channelId) {
- Object module = this.module;
- if (module != null) {
- try {
- if (CHANNEL_BATTERY_LEVEL.equalsIgnoreCase(channelId)
- || CHANNEL_LOW_BATTERY.equalsIgnoreCase(channelId)) {
- switch (channelId) {
- case CHANNEL_BATTERY_LEVEL:
- Method getBatteryPercent = module.getClass().getMethod("getBatteryPercent");
- Integer batteryPercent = (Integer) getBatteryPercent.invoke(module);
- return Optional.of(ChannelTypeUtils.toDecimalType(batteryPercent));
- case CHANNEL_LOW_BATTERY:
- Method getBatteryVp = module.getClass().getMethod("getBatteryVp");
- Integer batteryVp = (Integer) getBatteryVp.invoke(module);
- return Optional.of(batteryVp < batteryLow ? OnOffType.ON : OnOffType.OFF);
- }
- }
- } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
- | InvocationTargetException e) {
- logger.warn("The module has no method to access {} property : {}", channelId, e.getMessage());
- return Optional.of(UnDefType.NULL);
- }
- }
- return Optional.empty();
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.channelhelper;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link RadioHelper} handle specific behavior
- * of WIFI or RF devices and modules
- *
- * @author Gaël L'hopital - Initial contribution
- *
- */
-@NonNullByDefault
-public class RadioHelper {
- private final Logger logger = LoggerFactory.getLogger(RadioHelper.class);
- private final List<Integer> signalThresholds;
- private @Nullable Object module;
-
- public RadioHelper(String signalLevels) {
- signalThresholds = Stream.of(signalLevels.split(",")).map(Integer::parseInt).collect(Collectors.toList());
- }
-
- private int getSignalStrength(int signalLevel) {
- int level;
- for (level = 0; level < signalThresholds.size(); level++) {
- if (signalLevel > signalThresholds.get(level)) {
- break;
- }
- }
- return level;
- }
-
- public void setModule(Object module) {
- this.module = module;
- }
-
- public Optional<State> getNAThingProperty(String channelId) {
- Object module = this.module;
- if (module != null) {
- try {
- switch (channelId) {
- case CHANNEL_RF_STATUS:
- Method getRfStatus = module.getClass().getMethod("getRfStatus");
- Integer rfStatus = (Integer) getRfStatus.invoke(module);
- return Optional.of(new DecimalType(getSignalStrength(rfStatus)));
- case CHANNEL_WIFI_STATUS:
- Method getWifiStatus = module.getClass().getMethod("getWifiStatus");
- Integer wifiStatus = (Integer) getWifiStatus.invoke(module);
- return Optional.of(new DecimalType(getSignalStrength(wifiStatus)));
- }
- } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
- | InvocationTargetException e) {
- logger.warn("The module has no method to access {} property : {}", channelId, e.getMessage());
- return Optional.of(UnDefType.NULL);
- }
- }
- return Optional.empty();
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.config;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+
+/**
+ * The {@link BindingConfiguration} is responsible for holding configuration of the binding itself.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class BindingConfiguration {
+ public Set<FeatureArea> features = Set.of();
+ public boolean readFriends = false;
+
+ public void update(BindingConfiguration newConfig) {
+ this.features = newConfig.features;
+ this.readFriends = newConfig.readFriends;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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 = "";
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 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;
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.deserialization;
+
+import java.time.Instant;
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link NADeserializer} is responsible to instantiate suitable Gson (de)serializer
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = NADeserializer.class)
+public class NADeserializer {
+ private final Gson gson;
+
+ @Activate
+ public NADeserializer(@Reference TimeZoneProvider timeZoneProvider) {
+ gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+ .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory())
+ .registerTypeAdapter(NAObjectMap.class, new NAObjectMapDeserializer())
+ .registerTypeAdapter(NAPushType.class, new NAPushTypeDeserializer())
+ .registerTypeAdapter(ZonedDateTime.class,
+ (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
+ long netatmoTS = json.getAsJsonPrimitive().getAsLong();
+ Instant i = Instant.ofEpochSecond(netatmoTS);
+ return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone());
+ })
+ .registerTypeAdapter(OnOffType.class,
+ (JsonDeserializer<OnOffType>) (json, type, jsonDeserializationContext) -> OnOffType
+ .from(json.getAsJsonPrimitive().getAsString()))
+ .registerTypeAdapter(OpenClosedType.class,
+ (JsonDeserializer<OpenClosedType>) (json, type, jsonDeserializationContext) -> {
+ String value = json.getAsJsonPrimitive().getAsString().toUpperCase();
+ return "TRUE".equals(value) || "1".equals(value) ? OpenClosedType.CLOSED
+ : OpenClosedType.OPEN;
+ })
+ .create();
+ }
+
+ public <T> T deserialize(Class<T> clazz, String json) throws NetatmoException {
+ try {
+ @Nullable
+ T result = gson.fromJson(json, clazz);
+ if (result != null) {
+ return result;
+ }
+ throw new NetatmoException("Deserialization of '%s' resulted in null value", json);
+ } catch (JsonSyntaxException e) {
+ throw new NetatmoException(e, "Unexpected error deserializing '%s'", json);
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.deserialization;
+
+import java.util.HashMap;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+
+/**
+ * The {@link NAObjectMap} defines a hashmap of NAObjects identified by their id.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NAObjectMap<T extends NAObject> extends HashMap<String, T> {
+ private static final long serialVersionUID = 7635233672795516649L;
+
+ @Nullable
+ public T put(T thing) {
+ return super.put(thing.getId(), thing);
+ }
+
+ public Optional<T> getOpt(String key) {
+ return Optional.ofNullable(super.get(key));
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.deserialization;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+/**
+ * The {@link NAObjectMapDeserializer} is a specialized deserializer aimed to transform
+ * a list of `NAObjects` into a map identified by the object's id.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+class NAObjectMapDeserializer implements JsonDeserializer<NAObjectMap<?>> {
+ @Override
+ public @Nullable NAObjectMap<?> deserialize(JsonElement json, Type clazz, JsonDeserializationContext context)
+ throws JsonParseException {
+ ParameterizedType parameterized = (ParameterizedType) clazz;
+ Type[] typeArguments = parameterized.getActualTypeArguments();
+ if (typeArguments.length > 0 && json instanceof JsonArray) {
+ Type objectType = typeArguments[0];
+ NAObjectMap<NAObject> result = new NAObjectMap<>();
+ ((JsonArray) json).forEach(item -> {
+ result.put(context.deserialize(item, objectType));
+ });
+ return result;
+ }
+ return null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.deserialization;
+
+import java.lang.reflect.Type;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+/**
+ * Specialized deserializer for push_type field
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+class NAPushTypeDeserializer implements JsonDeserializer<NAPushType> {
+
+ @Override
+ public @Nullable NAPushType deserialize(JsonElement json, Type clazz, JsonDeserializationContext context)
+ throws JsonParseException {
+ String string = json.getAsString();
+ String[] elements = string.split("-");
+ if (elements.length > 1) {
+ try {
+ ModuleType moduleType = ModuleType.from(elements[0]);
+ EventType eventType = EventType.valueOf(elements[1].toUpperCase());
+
+ return new NAPushType(moduleType, eventType);
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ throw new JsonParseException("Error deserializing : " + string);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.deserialization;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * Enforces a fallback to UNKNOWN when deserializing enum types, marked as @NonNull whereas they were valued
+ * to null if the appropriate value is absent. It will give more resilience to the binding when Netatmo API evolves.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+class StrictEnumTypeAdapterFactory implements TypeAdapterFactory {
+ private static final StringReader UNKNOWN = new StringReader("\"UNKNOWN\"");
+
+ @Override
+ public @Nullable <T> TypeAdapter<T> create(@NonNullByDefault({}) Gson gson,
+ @NonNullByDefault({}) TypeToken<T> type) {
+ @SuppressWarnings("unchecked")
+ Class<T> rawType = (Class<T>) type.getRawType();
+ return rawType.isEnum() ? newStrictEnumAdapter(gson.getDelegateAdapter(this, type)) : null;
+ }
+
+ private <T> TypeAdapter<T> newStrictEnumAdapter(@NonNullByDefault({}) TypeAdapter<T> delegateAdapter) {
+ return new TypeAdapter<T>() {
+ @Override
+ public void write(JsonWriter out, @Nullable T value) throws IOException {
+ delegateAdapter.write(out, value);
+ }
+
+ @Override
+ public @Nullable T read(JsonReader in) throws IOException {
+ JsonReader delegateReader = new JsonReader(new StringReader('"' + in.nextString() + '"'));
+ @Nullable
+ T value = delegateAdapter.read(delegateReader);
+ delegateReader.close();
+ if (value == null) {
+ UNKNOWN.reset();
+ value = delegateAdapter.read(new JsonReader(UNKNOWN));
+ }
+ return value;
+ }
+ };
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.discovery;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.EQUIPMENT_ID;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.AircareApi;
+import org.openhab.binding.netatmo.internal.api.HomeApi;
+import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.WeatherApi;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain;
+import org.openhab.binding.netatmo.internal.api.dto.NAModule;
+import org.openhab.binding.netatmo.internal.config.BindingConfiguration;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link NetatmoDiscoveryService} searches for available Netatmo things
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class NetatmoDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService, DiscoveryService {
+ private static final Set<ModuleType> SKIPPED_TYPES = Set.of(ModuleType.UNKNOWN, ModuleType.ACCOUNT);
+ private static final int DISCOVER_TIMEOUT_SECONDS = 5;
+ private final Logger logger = LoggerFactory.getLogger(NetatmoDiscoveryService.class);
+ private @Nullable ApiBridgeHandler handler;
+ private @Nullable BindingConfiguration config;
+
+ public NetatmoDiscoveryService() {
+ super(ModuleType.AS_SET.stream().filter(mt -> !SKIPPED_TYPES.contains(mt)).map(mt -> mt.thingTypeUID)
+ .collect(Collectors.toSet()), DISCOVER_TIMEOUT_SECONDS);
+ }
+
+ @Override
+ public void startScan() {
+ BindingConfiguration localConf = config;
+ ApiBridgeHandler localHandler = handler;
+ if (localHandler != null && localConf != null) {
+ ThingUID apiBridgeUID = localHandler.getThing().getUID();
+ try {
+ AircareApi airCareApi = localHandler.getRestManager(AircareApi.class);
+ if (airCareApi != null) { // Search Healthy Home Coaches
+ ListBodyResponse<NAMain> body = airCareApi.getHomeCoachData(null).getBody();
+ if (body != null) {
+ body.getElements().stream().forEach(homeCoach -> createThing(homeCoach, apiBridgeUID));
+ }
+ }
+ if (localConf.readFriends) {
+ WeatherApi weatherApi = localHandler.getRestManager(WeatherApi.class);
+ if (weatherApi != null) { // Search favorite stations
+ ListBodyResponse<NAMain> body = weatherApi.getStationsData(null, true).getBody();
+ if (body != null) {
+ body.getElements().stream().filter(NAMain::isReadOnly).forEach(station -> {
+ ThingUID bridgeUID = createThing(station, apiBridgeUID);
+ station.getModules().values().stream()
+ .forEach(module -> createThing(module, bridgeUID));
+ });
+ }
+ }
+ }
+ HomeApi homeApi = localHandler.getRestManager(HomeApi.class);
+ if (homeApi != null) { // Search all the rest
+ homeApi.getHomesData(null, null).stream().filter(h -> !h.getFeatures().isEmpty()).forEach(home -> {
+ ThingUID homeUID = createThing(home, apiBridgeUID);
+ home.getKnownPersons().forEach(person -> createThing(person, homeUID));
+ home.getModules().values().stream().forEach(device -> {
+ ModuleType deviceType = device.getType();
+ String deviceBridge = device.getBridge();
+ ThingUID bridgeUID = deviceBridge != null && deviceType.getBridge() != ModuleType.HOME
+ ? findThingUID(deviceType.getBridge(), deviceBridge, apiBridgeUID)
+ : deviceType.getBridge() == ModuleType.HOME ? homeUID : apiBridgeUID;
+ createThing(device, bridgeUID);
+ });
+ home.getRooms().values().stream().forEach(room -> {
+ room.getModuleIds().stream().map(id -> home.getModules().get(id))
+ .map(m -> m != null ? m.getType().feature : FeatureArea.NONE)
+ .filter(f -> FeatureArea.ENERGY.equals(f)).findAny()
+ .ifPresent(f -> createThing(room, homeUID));
+ });
+ });
+ }
+ } catch (NetatmoException e) {
+ logger.warn("Error during discovery process : {}", e.getMessage());
+ }
+ }
+ }
+
+ private ThingUID findThingUID(ModuleType thingType, String thingId, @Nullable ThingUID brigdeUID) {
+ for (ThingTypeUID supported : getSupportedThingTypes()) {
+ ThingTypeUID thingTypeUID = thingType.thingTypeUID;
+ if (supported.equals(thingTypeUID)) {
+ String id = thingId.replaceAll("[^a-zA-Z0-9_]", "");
+ return brigdeUID == null ? new ThingUID(supported, id) : new ThingUID(supported, brigdeUID, id);
+ }
+ }
+ throw new IllegalArgumentException("Unsupported device type discovered : " + thingType);
+ }
+
+ private ThingUID createThing(NAModule module, @Nullable ThingUID bridgeUID) {
+ ThingUID moduleUID = findThingUID(module.getType(), module.getId(), bridgeUID);
+ DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(moduleUID)
+ .withProperty(EQUIPMENT_ID, module.getId()).withRepresentationProperty(EQUIPMENT_ID)
+ .withLabel(module.getName() != null ? module.getName() : module.getId());
+ if (bridgeUID != null) {
+ resultBuilder.withBridge(bridgeUID);
+ }
+ thingDiscovered(resultBuilder.build());
+ return moduleUID;
+ }
+
+ @Override
+ public void setThingHandler(ThingHandler handler) {
+ if (handler instanceof ApiBridgeHandler) {
+ this.handler = (ApiBridgeHandler) handler;
+ this.config = ((ApiBridgeHandler) handler).getConfiguration();
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return handler;
+ }
+
+ @Override
+ public void deactivate() {
+ super.deactivate();
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.discovery;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
-import org.openhab.binding.netatmo.internal.handler.NetatmoDataListener;
-import org.openhab.core.config.discovery.AbstractDiscoveryService;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.i18n.TranslationProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-
-import io.swagger.client.model.NAHealthyHomeCoach;
-import io.swagger.client.model.NAMain;
-import io.swagger.client.model.NAPlug;
-import io.swagger.client.model.NAStationModule;
-import io.swagger.client.model.NAWelcomeCamera;
-import io.swagger.client.model.NAWelcomeHome;
-
-/**
- * The {@link NetatmoModuleDiscoveryService} searches for available Netatmo
- * devices and modules connected to the API console
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Ing. Peter Weiss - Welcome camera implementation
- *
- */
-@NonNullByDefault
-public class NetatmoModuleDiscoveryService extends AbstractDiscoveryService implements NetatmoDataListener {
- private static final int SEARCH_TIME = 5;
- private final NetatmoBridgeHandler netatmoBridgeHandler;
-
- public NetatmoModuleDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler, LocaleProvider localeProvider,
- TranslationProvider translationProvider) {
- super(SUPPORTED_DEVICE_THING_TYPES_UIDS, SEARCH_TIME);
- this.netatmoBridgeHandler = netatmoBridgeHandler;
- this.localeProvider = localeProvider;
- this.i18nProvider = translationProvider;
- }
-
- @Override
- public void activate(@Nullable Map<String, Object> configProperties) {
- super.activate(configProperties);
- netatmoBridgeHandler.registerDataListener(this);
- }
-
- @Override
- public void deactivate() {
- netatmoBridgeHandler.unregisterDataListener(this);
- super.deactivate();
- }
-
- @Override
- public void startScan() {
- if (netatmoBridgeHandler.configuration.readStation) {
- netatmoBridgeHandler.getStationsDataBody(null).ifPresent(dataBody -> {
- nonNullList(dataBody.getDevices()).forEach(station -> {
- discoverWeatherStation(station);
- });
- });
- }
- if (netatmoBridgeHandler.configuration.readHealthyHomeCoach) {
- netatmoBridgeHandler.getHomecoachDataBody(null).ifPresent(dataBody -> {
- nonNullList(dataBody.getDevices()).forEach(homecoach -> {
- discoverHomeCoach(homecoach);
- });
- });
- }
- if (netatmoBridgeHandler.configuration.readThermostat) {
- netatmoBridgeHandler.getThermostatsDataBody(null).ifPresent(dataBody -> {
- nonNullList(dataBody.getDevices()).forEach(plug -> {
- discoverThermostat(plug);
- });
- });
- }
- if (netatmoBridgeHandler.configuration.readWelcome || netatmoBridgeHandler.configuration.readPresence) {
- netatmoBridgeHandler.getWelcomeDataBody(null).ifPresent(dataBody -> {
- nonNullList(dataBody.getHomes()).forEach(home -> {
- discoverWelcomeHome(home);
- });
- });
- }
- }
-
- @Override
- protected synchronized void stopScan() {
- super.stopScan();
- removeOlderResults(getTimestampOfLastScan(), netatmoBridgeHandler.getThing().getUID());
- }
-
- @Override
- public void onDataRefreshed(Object data) {
- if (!isBackgroundDiscoveryEnabled()) {
- return;
- }
- if (data instanceof NAMain) {
- discoverWeatherStation((NAMain) data);
- } else if (data instanceof NAPlug) {
- discoverThermostat((NAPlug) data);
- } else if (data instanceof NAHealthyHomeCoach) {
- discoverHomeCoach((NAHealthyHomeCoach) data);
- } else if (data instanceof NAWelcomeHome) {
- discoverWelcomeHome((NAWelcomeHome) data);
- }
- }
-
- private void discoverThermostat(NAPlug plug) {
- onDeviceAddedInternal(plug.getId(), null, plug.getType(), plug.getStationName(), plug.getFirmware());
- nonNullList(plug.getModules()).forEach(thermostat -> {
- onDeviceAddedInternal(thermostat.getId(), plug.getId(), thermostat.getType(), thermostat.getModuleName(),
- thermostat.getFirmware());
- });
- }
-
- private void discoverHomeCoach(NAHealthyHomeCoach homecoach) {
- onDeviceAddedInternal(homecoach.getId(), null, homecoach.getType(), homecoach.getName(),
- homecoach.getFirmware());
- }
-
- private void discoverWeatherStation(NAMain station) {
- final boolean isFavorite = station.isFavorite() != null && station.isFavorite();
- final String weatherStationName = createWeatherStationName(station, isFavorite);
-
- onDeviceAddedInternal(station.getId(), null, station.getType(), weatherStationName, station.getFirmware());
- nonNullList(station.getModules()).forEach(module -> {
- onDeviceAddedInternal(module.getId(), station.getId(), module.getType(),
- createWeatherModuleName(station, module, isFavorite), module.getFirmware());
- });
- }
-
- private void discoverWelcomeHome(NAWelcomeHome home) {
- // I observed that Thermostat homes are also reported here by Netatmo API
- // So I ignore homes that have an empty list of cameras
- List<NAWelcomeCamera> cameras = nonNullList(home.getCameras());
- if (!cameras.isEmpty()) {
- onDeviceAddedInternal(home.getId(), null, WELCOME_HOME_THING_TYPE.getId(), home.getName(), null);
- // Discover Cameras
- cameras.forEach(camera -> {
- onDeviceAddedInternal(camera.getId(), home.getId(), camera.getType(), camera.getName(), null);
- });
-
- // Discover Known Persons
- nonNullStream(home.getPersons()).filter(person -> person.getPseudo() != null).forEach(person -> {
- onDeviceAddedInternal(person.getId(), home.getId(), WELCOME_PERSON_THING_TYPE.getId(),
- person.getPseudo(), null);
- });
- }
- }
-
- private void onDeviceAddedInternal(String id, @Nullable String parentId, String type, String name,
- @Nullable Integer firmwareVersion) {
- ThingUID thingUID = findThingUID(type, id);
- Map<String, Object> properties = new HashMap<>();
-
- properties.put(EQUIPMENT_ID, id);
- if (parentId != null) {
- properties.put(PARENT_ID, parentId);
- }
- if (firmwareVersion != null) {
- properties.put(Thing.PROPERTY_VENDOR, VENDOR);
- properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
- properties.put(Thing.PROPERTY_MODEL_ID, type);
- properties.put(Thing.PROPERTY_SERIAL_NUMBER, id);
- }
- addDiscoveredThing(thingUID, properties, name);
- }
-
- private void addDiscoveredThing(ThingUID thingUID, Map<String, Object> properties, String displayLabel) {
- DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
- .withBridge(netatmoBridgeHandler.getThing().getUID()).withLabel(displayLabel)
- .withRepresentationProperty(EQUIPMENT_ID).build();
-
- thingDiscovered(discoveryResult);
- }
-
- private ThingUID findThingUID(String thingType, String thingId) throws IllegalArgumentException {
- for (ThingTypeUID supportedThingTypeUID : getSupportedThingTypes()) {
- String uid = supportedThingTypeUID.getId();
-
- if (uid.equalsIgnoreCase(thingType)) {
- return new ThingUID(supportedThingTypeUID, netatmoBridgeHandler.getThing().getUID(),
- thingId.replaceAll("[^a-zA-Z0-9_]", ""));
- }
- }
-
- throw new IllegalArgumentException("Unsupported device type discovered : " + thingType);
- }
-
- private String createWeatherStationName(NAMain station, boolean isFavorite) {
- StringBuilder nameBuilder = new StringBuilder();
- nameBuilder.append(localizeType(station.getType()));
- if (station.getStationName() != null) {
- nameBuilder.append(' ');
- nameBuilder.append(station.getStationName());
- }
- if (isFavorite) {
- nameBuilder.append(" (favorite)");
- }
- return nameBuilder.toString();
- }
-
- private String createWeatherModuleName(NAMain station, NAStationModule module, boolean isFavorite) {
- StringBuilder nameBuilder = new StringBuilder();
- if (module.getModuleName() != null) {
- nameBuilder.append(module.getModuleName());
- } else {
- nameBuilder.append(localizeType(module.getType()));
- }
- if (station.getStationName() != null) {
- nameBuilder.append(' ');
- nameBuilder.append(station.getStationName());
- }
- if (isFavorite) {
- nameBuilder.append(" (favorite)");
- }
- return nameBuilder.toString();
- }
-
- private String localizeType(String typeName) {
- Bundle bundle = FrameworkUtil.getBundle(this.getClass());
- @Nullable
- String localizedType = i18nProvider.getText(bundle, "thing-type.netatmo." + typeName + ".label", typeName,
- localeProvider.getLocale());
- if (localizedType != null) {
- return localizedType;
- }
- return typeName;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.handler;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-import static org.openhab.core.library.unit.MetricPrefix.*;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-import javax.measure.Unit;
-import javax.measure.quantity.Angle;
-import javax.measure.quantity.Dimensionless;
-import javax.measure.quantity.Length;
-import javax.measure.quantity.Pressure;
-import javax.measure.quantity.Speed;
-import javax.measure.quantity.Temperature;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.channelhelper.BatteryHelper;
-import org.openhab.binding.netatmo.internal.channelhelper.RadioHelper;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.unit.SIUnits;
-import org.openhab.core.library.unit.Units;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingStatusInfo;
-import org.openhab.core.thing.binding.BaseThingHandler;
-import org.openhab.core.thing.binding.BridgeHandler;
-import org.openhab.core.thing.type.ChannelKind;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * {@link AbstractNetatmoThingHandler} is the abstract class that handles
- * common behaviors of all netatmo things
- *
- * @author Gaël L'hopital - Initial contribution OH2 version
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public abstract class AbstractNetatmoThingHandler extends BaseThingHandler {
- // Units of measurement of the data delivered by the API
- public static final Unit<Temperature> API_TEMPERATURE_UNIT = SIUnits.CELSIUS;
- public static final Unit<Dimensionless> API_HUMIDITY_UNIT = Units.PERCENT;
- public static final Unit<Pressure> API_PRESSURE_UNIT = HECTO(SIUnits.PASCAL);
- public static final Unit<Speed> API_WIND_SPEED_UNIT = SIUnits.KILOMETRE_PER_HOUR;
- public static final Unit<Angle> API_WIND_DIRECTION_UNIT = Units.DEGREE_ANGLE;
- public static final Unit<Length> API_RAIN_UNIT = MILLI(SIUnits.METRE);
- public static final Unit<Dimensionless> API_CO2_UNIT = Units.PARTS_PER_MILLION;
- public static final Unit<Dimensionless> API_NOISE_UNIT = Units.DECIBEL;
-
- private final Logger logger = LoggerFactory.getLogger(AbstractNetatmoThingHandler.class);
-
- protected final TimeZoneProvider timeZoneProvider;
- private @Nullable RadioHelper radioHelper;
- private @Nullable BatteryHelper batteryHelper;
- protected @Nullable Configuration config;
- private @Nullable NetatmoBridgeHandler bridgeHandler;
-
- AbstractNetatmoThingHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing);
- this.timeZoneProvider = timeZoneProvider;
- }
-
- @Override
- public void initialize() {
- logger.debug("initializing handler for thing {}", getThing().getUID());
- Bridge bridge = getBridge();
- initializeThing(bridge != null ? bridge.getStatus() : null);
- }
-
- @Override
- public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
- logger.debug("bridgeStatusChanged {} for thing {}", bridgeStatusInfo, getThing().getUID());
- initializeThing(bridgeStatusInfo.getStatus());
- }
-
- private void initializeThing(@Nullable ThingStatus bridgeStatus) {
- Bridge bridge = getBridge();
- BridgeHandler bridgeHandler = bridge != null ? bridge.getHandler() : null;
- if (bridgeHandler != null && bridgeStatus != null) {
- if (bridgeStatus == ThingStatus.ONLINE) {
- config = getThing().getConfiguration();
-
- String signalLevel = thing.getProperties().get(PROPERTY_SIGNAL_LEVELS);
- radioHelper = signalLevel != null ? new RadioHelper(signalLevel) : null;
- String batteryLevel = thing.getProperties().get(PROPERTY_BATTERY_LEVELS);
- batteryHelper = batteryLevel != null ? new BatteryHelper(batteryLevel) : null;
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Pending parent object initialization");
-
- initializeThing();
- } else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
- }
- } else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
- }
- }
-
- protected abstract void initializeThing();
-
- protected State getNAThingProperty(String channelId) {
- Optional<State> result = getBatteryHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
- if (result.isPresent()) {
- return result.get();
- }
- result = getRadioHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
- if (result.isPresent()) {
- return result.get();
- }
- return UnDefType.UNDEF;
- }
-
- protected void updateChannels() {
- if (thing.getStatus() != ThingStatus.ONLINE) {
- return;
- }
-
- updateDataChannels();
-
- triggerEventChannels();
- }
-
- private void updateDataChannels() {
- getThing().getChannels().stream()
- .filter(channel -> !ChannelKind.TRIGGER.equals(channel.getKind()) && isLinked(channel.getUID()))
- .map(channel -> channel.getUID()).forEach(this::updateChannel);
- }
-
- private void updateChannel(ChannelUID channelUID) {
- updateState(channelUID, getNAThingProperty(channelUID.getId()));
- }
-
- /**
- * Triggers all event/trigger channels
- * (when a channel is triggered, a rule can get all other information from the updated non-trigger channels)
- */
- private void triggerEventChannels() {
- getThing().getChannels().stream().filter(channel -> ChannelKind.TRIGGER.equals(channel.getKind()))
- .map(channel -> channel.getUID().getId()).forEach(this::triggerChannelIfRequired);
- }
-
- /**
- * Triggers the trigger channel with the given channel id when required (when an update is available)
- *
- * @param channelId channel id
- */
- protected void triggerChannelIfRequired(String channelId) {
- }
-
- @Override
- public void handleCommand(ChannelUID channelUID, Command command) {
- if (command == RefreshType.REFRESH) {
- logger.debug("Refreshing '{}'", channelUID);
- updateChannel(channelUID);
- }
- }
-
- protected Optional<NetatmoBridgeHandler> getBridgeHandler() {
- if (bridgeHandler == null) {
- Bridge bridge = getBridge();
- if (bridge != null) {
- bridgeHandler = (NetatmoBridgeHandler) bridge.getHandler();
- }
- }
- NetatmoBridgeHandler handler = bridgeHandler;
- return handler != null ? Optional.of(handler) : Optional.empty();
- }
-
- protected Optional<AbstractNetatmoThingHandler> findNAThing(@Nullable String searchedId) {
- return getBridgeHandler().flatMap(handler -> handler.findNAThing(searchedId));
- }
-
- public boolean matchesId(@Nullable String searchedId) {
- return searchedId != null && searchedId.equalsIgnoreCase(getId());
- }
-
- protected @Nullable String getId() {
- Configuration conf = config;
- Object equipmentId = conf != null ? conf.get(EQUIPMENT_ID) : null;
- if (equipmentId instanceof String) {
- return ((String) equipmentId).toLowerCase();
- }
- return null;
- }
-
- protected void updateProperties(@Nullable Integer firmware, @Nullable String modelId) {
- Map<String, String> properties = editProperties();
- if (firmware != null || modelId != null) {
- properties.put(Thing.PROPERTY_VENDOR, VENDOR);
- }
- if (firmware != null) {
- properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmware.toString());
- }
- if (modelId != null) {
- properties.put(Thing.PROPERTY_MODEL_ID, modelId);
- }
- updateProperties(properties);
- }
-
- protected Optional<RadioHelper> getRadioHelper() {
- RadioHelper helper = radioHelper;
- return helper != null ? Optional.of(helper) : Optional.empty();
- }
-
- protected Optional<BatteryHelper> getBatteryHelper() {
- BatteryHelper helper = batteryHelper;
- return helper != null ? Optional.of(helper) : Optional.empty();
- }
-
- public void updateMeasurements() {
- }
-
- public void getMeasurements(@Nullable String device, @Nullable String module, String scale, List<String> types,
- List<String> channels, Map<String, Float> channelMeasurements) {
- Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
- if (!handler.isPresent() || device == null) {
- return;
- }
-
- if (types.size() != channels.size()) {
- throw new IllegalArgumentException("types and channels lists are different sizes.");
- }
-
- List<Float> measurements = handler.get().getStationMeasureResponses(device, module, scale, types);
- if (measurements.size() != types.size()) {
- throw new IllegalArgumentException("types and measurements lists are different sizes.");
- }
-
- int i = 0;
- for (Float measurement : measurements) {
- channelMeasurements.put(channels.get(i++), measurement);
- }
- }
-
- public void addMeasurement(List<String> channels, List<String> types, String channel, String type) {
- if (isLinked(channel)) {
- channels.add(channel);
- types.add(type);
- }
- }
-
- protected boolean isReachable() {
- return true;
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.InputStreamContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpStatus.Code;
+import org.openhab.binding.netatmo.internal.api.ApiError;
+import org.openhab.binding.netatmo.internal.api.AuthenticationApi;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.RestManager;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
+import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration;
+import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration.Credentials;
+import org.openhab.binding.netatmo.internal.config.BindingConfiguration;
+import org.openhab.binding.netatmo.internal.deserialization.NADeserializer;
+import org.openhab.binding.netatmo.internal.discovery.NetatmoDiscoveryService;
+import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.osgi.service.http.HttpService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link ApiBridgeHandler} is the handler for a Netatmo API and connects it to the framework.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ApiBridgeHandler extends BaseBridgeHandler {
+ private static final int TIMEOUT_S = 20;
+
+ private final Logger logger = LoggerFactory.getLogger(ApiBridgeHandler.class);
+ private final BindingConfiguration bindingConf;
+ private final HttpService httpService;
+ private final AuthenticationApi connectApi;
+ private final HttpClient httpClient;
+ private final NADeserializer deserializer;
+
+ private Optional<ScheduledFuture<?>> connectJob = Optional.empty();
+ private Optional<NetatmoServlet> servlet = Optional.empty();
+ private @NonNullByDefault({}) ApiHandlerConfiguration thingConf;
+
+ private Map<Class<? extends RestManager>, RestManager> managers = new HashMap<>();
+
+ public ApiBridgeHandler(Bridge bridge, HttpClient httpClient, HttpService httpService, NADeserializer deserializer,
+ BindingConfiguration configuration) {
+ super(bridge);
+ this.bindingConf = configuration;
+ this.httpService = httpService;
+ this.connectApi = new AuthenticationApi(this, scheduler);
+ this.httpClient = httpClient;
+ this.deserializer = deserializer;
+ }
+
+ @Override
+ public void initialize() {
+ logger.debug("Initializing Netatmo API bridge handler.");
+ thingConf = getConfigAs(ApiHandlerConfiguration.class);
+ updateStatus(ThingStatus.UNKNOWN);
+ scheduler.execute(() -> {
+ openConnection();
+ String webHookUrl = thingConf.webHookUrl;
+ if (webHookUrl != null && !webHookUrl.isBlank()) {
+ servlet = Optional.of(new NetatmoServlet(httpService, this, webHookUrl));
+ }
+ });
+ }
+
+ private void openConnection() {
+ try {
+ Credentials credentials = thingConf.getCredentials();
+ logger.debug("Connecting to Netatmo API.");
+ try {
+ connectApi.authenticate(credentials, bindingConf.features);
+ updateStatus(ThingStatus.ONLINE);
+ getThing().getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler).filter(Objects::nonNull)
+ .map(CommonInterface.class::cast).forEach(CommonInterface::expireData);
+ } catch (NetatmoException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ prepareReconnection();
+ }
+ } catch (NetatmoException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
+ }
+ }
+
+ private void prepareReconnection() {
+ connectApi.disconnect();
+ freeConnectJob();
+ connectJob = Optional
+ .of(scheduler.schedule(() -> openConnection(), thingConf.reconnectInterval, TimeUnit.SECONDS));
+ }
+
+ private void freeConnectJob() {
+ connectJob.ifPresent(j -> j.cancel(true));
+ connectJob = Optional.empty();
+ }
+
+ @Override
+ public void dispose() {
+ logger.debug("Shutting down Netatmo API bridge handler.");
+ servlet.ifPresent(servlet -> servlet.dispose());
+ servlet = Optional.empty();
+ connectApi.dispose();
+ freeConnectJob();
+ super.dispose();
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("Netatmo Bridge is read-only and does not handle commands");
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Set.of(NetatmoDiscoveryService.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends RestManager> @Nullable T getRestManager(Class<T> clazz) {
+ if (!managers.containsKey(clazz)) {
+ try {
+ Constructor<T> constructor = clazz.getConstructor(getClass());
+ T instance = constructor.newInstance(this);
+ Set<Scope> expected = instance.getRequiredScopes();
+ if (connectApi.matchesScopes(expected)) {
+ managers.put(clazz, instance);
+ } else {
+ logger.info("Unable to instantiate {}, expected scope {} is not active", clazz, expected);
+ }
+ } catch (SecurityException | ReflectiveOperationException e) {
+ logger.warn("Error invoking RestManager constructor for class {} : {}", clazz, e.getMessage());
+ }
+ }
+ return (T) managers.get(clazz);
+ }
+
+ public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nullable String payload,
+ @Nullable String contentType, int retryCount) throws NetatmoException {
+ try {
+ logger.trace("executeUri {} {} ", method.toString(), uri);
+
+ Request request = httpClient.newRequest(uri).method(method).timeout(TIMEOUT_S, TimeUnit.SECONDS);
+
+ String auth = connectApi.getAuthorization();
+ if (auth != null) {
+ request.header(HttpHeader.AUTHORIZATION, auth);
+ }
+
+ if (payload != null && contentType != null
+ && (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method))) {
+ InputStream stream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
+ try (InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider(stream)) {
+ request.content(inputStreamContentProvider, contentType);
+ }
+ }
+
+ ContentResponse response = request.send();
+
+ Code statusCode = HttpStatus.getCode(response.getStatus());
+ String responseBody = new String(response.getContent(), StandardCharsets.UTF_8);
+ logger.trace("executeUri returned : code {} body {}", statusCode, responseBody);
+
+ if (statusCode != Code.OK) {
+ ApiError error = deserializer.deserialize(ApiError.class, responseBody);
+ throw new NetatmoException(error);
+ }
+ return deserializer.deserialize(clazz, responseBody);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
+ } catch (TimeoutException | ExecutionException e) {
+ if (retryCount > 0) {
+ logger.debug("Request timedout, retry counter : {}", retryCount);
+ return executeUri(uri, method, clazz, payload, contentType, retryCount - 1);
+ }
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/request-time-out");
+ prepareReconnection();
+ throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
+ }
+ }
+
+ public BindingConfiguration getConfiguration() {
+ return bindingConf;
+ }
+
+ public Optional<NetatmoServlet> getServlet() {
+ return servlet;
+ }
+
+ public NADeserializer getDeserializer() {
+ return deserializer;
+ }
+
+ public boolean isConnected() {
+ return connectApi.isConnected();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.config.NAThingConfiguration;
+import org.openhab.binding.netatmo.internal.handler.capability.Capability;
+import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
+import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.RestCapability;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BridgeHandler;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.thing.type.ChannelKind;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+
+/**
+ * {@link CommonInterface} defines common methods of AccountHandler and NAThingHandlers used by Capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public interface CommonInterface {
+ Thing getThing();
+
+ ThingBuilder editThing();
+
+ CapabilityMap getCapabilities();
+
+ Logger getLogger();
+
+ ScheduledExecutorService getScheduler();
+
+ boolean isLinked(ChannelUID channelUID);
+
+ void updateState(ChannelUID channelUID, State state);
+
+ void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail,
+ @Nullable String thingStatusReason);
+
+ void triggerChannel(String channelID, String event);
+
+ void updateThing(Thing thing);
+
+ @Nullable
+ Bridge getBridge();
+
+ default @Nullable CommonInterface getBridgeHandler() {
+ Bridge bridge = getBridge();
+ return bridge != null && bridge.getHandler() instanceof DeviceHandler ? (DeviceHandler) bridge.getHandler()
+ : null;
+ }
+
+ default @Nullable ApiBridgeHandler getAccountHandler() {
+ Bridge bridge = getBridge();
+ BridgeHandler bridgeHandler = null;
+ if (bridge != null) {
+ bridgeHandler = bridge.getHandler();
+ while (bridgeHandler != null && !(bridgeHandler instanceof ApiBridgeHandler)) {
+ bridge = ((CommonInterface) bridgeHandler).getBridge();
+ bridgeHandler = bridge != null ? bridge.getHandler() : null;
+ }
+ }
+ return (ApiBridgeHandler) bridgeHandler;
+ }
+
+ default @Nullable String getBridgeId() {
+ CommonInterface bridge = getBridgeHandler();
+ return bridge != null ? bridge.getId() : null;
+ }
+
+ default void expireData() {
+ getCapabilities().values().forEach(cap -> cap.expireData());
+ }
+
+ default String getId() {
+ return (String) getThing().getConfiguration().get("id");
+ }
+
+ default Stream<Channel> getActiveChannels() {
+ return getThing().getChannels().stream()
+ .filter(channel -> ChannelKind.STATE.equals(channel.getKind()) && isLinked(channel.getUID()));
+ }
+
+ default Optional<CommonInterface> getHomeHandler() {
+ CommonInterface bridgeHandler = getBridgeHandler();
+ if (bridgeHandler != null) {
+ return bridgeHandler.getCapabilities().get(HomeCapability.class).isPresent() ? Optional.of(bridgeHandler)
+ : Optional.empty();
+ }
+ return Optional.empty();
+ }
+
+ default List<CommonInterface> getActiveChildren() {
+ Thing thing = getThing();
+ if (thing instanceof Bridge) {
+ return ((Bridge) thing).getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler)
+ .filter(Objects::nonNull).map(CommonInterface.class::cast).collect(Collectors.toList());
+ }
+ return List.of();
+ }
+
+ default <T extends RestCapability<?>> Optional<T> getHomeCapability(Class<T> clazz) {
+ return getHomeHandler().map(handler -> handler.getCapabilities().get(clazz)).orElse(Optional.empty());
+ }
+
+ default void setNewData(NAObject newData) {
+ String finalReason = null;
+ for (Capability cap : getCapabilities().values()) {
+ String thingStatusReason = cap.setNewData(newData);
+ if (thingStatusReason != null) {
+ finalReason = thingStatusReason;
+ }
+ }
+ if (!newData.isIgnoredForThingUpdate()) {
+ setThingStatus(finalReason == null ? ThingStatus.ONLINE : ThingStatus.OFFLINE, ThingStatusDetail.NONE,
+ finalReason);
+ }
+ }
+
+ default void commonHandleCommand(ChannelUID channelUID, Command command) {
+ if (ThingStatus.ONLINE.equals(getThing().getStatus())) {
+ if (command == RefreshType.REFRESH) {
+ expireData();
+ return;
+ }
+ String channelName = channelUID.getIdWithoutGroup();
+ getCapabilities().values().forEach(cap -> cap.handleCommand(channelName, command));
+ } else {
+ getLogger().debug("Command {}, on channel {} dropped - thing is not ONLINE", command, channelUID);
+ }
+ }
+
+ default void proceedWithUpdate() {
+ updateReadings().forEach(dataSet -> setNewData(dataSet));
+ }
+
+ default List<NAObject> updateReadings() {
+ List<NAObject> result = new ArrayList<>();
+ getCapabilities().values().forEach(cap -> result.addAll(cap.updateReadings()));
+ getActiveChildren().forEach(child -> result.addAll(child.updateReadings()));
+ return result;
+ }
+
+ default void commonInitialize() {
+ Bridge bridge = getBridge();
+ if (bridge == null || bridge.getHandler() == null) {
+ setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, null);
+ } else if (!ThingStatus.ONLINE.equals(bridge.getStatus())) {
+ setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
+ removeRefreshCapability();
+ } else {
+ setThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null);
+ setRefreshCapability();
+ getCapabilities().values().forEach(cap -> cap.initialize());
+ getScheduler().schedule(() -> {
+ CommonInterface bridgeHandler = getBridgeHandler();
+ if (bridgeHandler != null) {
+ bridgeHandler.expireData();
+ }
+ }, 1, TimeUnit.SECONDS);
+ }
+ }
+
+ default void setRefreshCapability() {
+ ModuleType moduleType = ModuleType.from(getThing().getThingTypeUID());
+ if (ModuleType.ACCOUNT.equals(moduleType.getBridge())) {
+ NAThingConfiguration config = getThing().getConfiguration().as(NAThingConfiguration.class);
+ getCapabilities().put(new RefreshCapability(this, getScheduler(), config.refreshInterval));
+ }
+ }
+
+ default void removeRefreshCapability() {
+ Capability refreshCap = getCapabilities().remove(RefreshCapability.class);
+ if (refreshCap != null) {
+ refreshCap.dispose();
+ }
+ }
+
+ default void commonDispose() {
+ getCapabilities().values().forEach(Capability::dispose);
+ }
+
+ default void removeChannels(List<Channel> channels) {
+ ThingBuilder builder = editThing().withoutChannels(channels);
+ updateThing(builder.build());
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link ModuleHandler} is the base class for all Netatmo things
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ModuleHandler extends BaseThingHandler implements CommonInterface {
+ private final Logger logger = LoggerFactory.getLogger(ModuleHandler.class);
+ private CapabilityMap capabilities = new CapabilityMap();
+
+ public ModuleHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ logger.debug("Initializing handler for thing {}", getThing().getUID());
+ commonInitialize();
+ }
+
+ @Override
+ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+ logger.debug("bridgeStatusChanged for thing {} to {}", getThing().getUID(), bridgeStatusInfo);
+ commonInitialize();
+ }
+
+ @Override
+ public void dispose() {
+ commonDispose();
+ super.dispose();
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ commonHandleCommand(channelUID, command);
+ }
+
+ @Override
+ public void setThingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail,
+ @Nullable String thingStatusReason) {
+ updateStatus(thingStatus, thingStatusDetail, thingStatusReason);
+ }
+
+ @Override
+ public CapabilityMap getCapabilities() {
+ return capabilities;
+ }
+
+ @Override
+ public ThingBuilder editThing() {
+ return super.editThing();
+ }
+
+ @Override
+ public void updateThing(Thing thing) {
+ super.updateThing(thing);
+ }
+
+ @Override
+ public void updateState(ChannelUID channelUID, State state) {
+ super.updateState(channelUID, state);
+ }
+
+ @Override
+ public boolean isLinked(ChannelUID channelUID) {
+ return super.isLinked(channelUID);
+ }
+
+ @Override
+ public @Nullable Bridge getBridge() {
+ return super.getBridge();
+ }
+
+ @Override
+ public void triggerChannel(String channelID, String event) {
+ super.triggerChannel(channelID, event);
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ List<Class<? extends ThingHandlerService>> result = new ArrayList<>();
+ capabilities.values().forEach(cap -> result.addAll(cap.getServices()));
+ return result;
+ }
+
+ @Override
+ public Logger getLogger() {
+ return logger;
+ }
+
+ @Override
+ public ScheduledExecutorService getScheduler() {
+ return scheduler;
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.handler;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
-
-import org.apache.oltu.oauth2.client.OAuthClient;
-import org.apache.oltu.oauth2.client.URLConnectionClient;
-import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
-import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
-import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
-import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
-import org.apache.oltu.oauth2.common.message.types.GrantType;
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.config.NetatmoBridgeConfiguration;
-import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
-import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEventPerson;
-import org.openhab.binding.netatmo.internal.webhook.WelcomeWebHookServlet;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.Channel;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.binding.BaseBridgeHandler;
-import org.openhab.core.types.Command;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.swagger.client.ApiClient;
-import io.swagger.client.ApiException;
-import io.swagger.client.api.HealthyhomecoachApi;
-import io.swagger.client.api.PartnerApi;
-import io.swagger.client.api.StationApi;
-import io.swagger.client.api.ThermostatApi;
-import io.swagger.client.api.WelcomeApi;
-import io.swagger.client.auth.Authentication;
-import io.swagger.client.auth.OAuth;
-import io.swagger.client.model.NAHealthyHomeCoachDataBody;
-import io.swagger.client.model.NAMeasureBodyElem;
-import io.swagger.client.model.NAStationDataBody;
-import io.swagger.client.model.NAThermostatDataBody;
-import io.swagger.client.model.NAWelcomeHomeData;
-
-/**
- * {@link NetatmoBridgeHandler} is the handler for a Netatmo API and connects it
- * to the framework. The devices and modules uses the
- * {@link NetatmoBridgeHandler} to request informations about their status
- *
- * @author Gaël L'hopital - Initial contribution OH2 version
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class NetatmoBridgeHandler extends BaseBridgeHandler {
- private final Logger logger = LoggerFactory.getLogger(NetatmoBridgeHandler.class);
-
- public NetatmoBridgeConfiguration configuration = new NetatmoBridgeConfiguration();
- private @Nullable ScheduledFuture<?> refreshJob;
- private @Nullable APICreator apiCreator;
- private @Nullable WelcomeWebHookServlet webHookServlet;
- private List<NetatmoDataListener> dataListeners = new CopyOnWriteArrayList<>();
-
- private static class APICreator {
-
- private final ApiClient apiClient;
- private final Map<Class<?>, Object> apiMap;
-
- private APICreator(ApiClient apiClient) {
- super();
- this.apiClient = apiClient;
- apiMap = new HashMap<>();
- }
-
- @SuppressWarnings("unchecked")
- public <T> T getAPI(Class<T> apiClass) {
- T api = (T) apiMap.get(apiClass);
- if (api == null) {
- try {
- api = apiClass.getDeclaredConstructor(ApiClient.class).newInstance(apiClient);
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException
- | NoSuchMethodException e) {
- throw new RuntimeException("Error on executing API class constructor!", e);
- }
- apiMap.put(apiClass, api);
- }
- return api;
- }
- }
-
- public NetatmoBridgeHandler(Bridge bridge, @Nullable WelcomeWebHookServlet webHookServlet) {
- super(bridge);
- this.webHookServlet = webHookServlet;
- }
-
- @Override
- public void initialize() {
- logger.debug("Initializing Netatmo API bridge handler.");
-
- configuration = getConfigAs(NetatmoBridgeConfiguration.class);
- scheduleTokenInitAndRefresh();
- }
-
- private void connectionSucceed() {
- updateStatus(ThingStatus.ONLINE);
- WelcomeWebHookServlet servlet = webHookServlet;
- String webHookURI = getWebHookURI();
- if (servlet != null && webHookURI != null) {
- getWelcomeApi().ifPresent(api -> {
- servlet.activate(this);
- logger.debug("Setting up Netatmo Welcome WebHook");
- api.addwebhook(webHookURI, WEBHOOK_APP);
- });
- }
- }
-
- private void scheduleTokenInitAndRefresh() {
- refreshJob = scheduler.scheduleWithFixedDelay(() -> {
- logger.debug("Initializing API Connection and scheduling token refresh every {}s",
- configuration.reconnectInterval);
- try {
- initializeApiClient();
- // I use a connection to Netatmo API using PartnerAPI to ensure that API is reachable
- getPartnerApi().partnerdevices();
- connectionSucceed();
- } catch (ApiException e) {
- switch (e.getCode()) {
- case 404: // If no partner station has been associated - likely to happen - we'll have this
- // error
- // but it means connection to API is OK
- connectionSucceed();
- break;
- case 403: // Forbidden Access maybe too many requests ? Let's wait next cycle
- logger.warn("Error 403 while connecting to Netatmo API, will retry in {} s",
- configuration.reconnectInterval);
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- "Netatmo Access Forbidden, will retry in " + configuration.reconnectInterval
- + " seconds.");
- break;
- default:
- if (logger.isDebugEnabled()) {
- // we also attach the stack trace
- logger.error("Unable to connect Netatmo API : {}", e.getMessage(), e);
- } else {
- logger.error("Unable to connect Netatmo API : {}", e.getMessage());
- }
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "Unable to connect Netatmo API : " + e.getLocalizedMessage());
- }
- } catch (RuntimeException e) {
- if (logger.isDebugEnabled()) {
- logger.warn("Unable to connect Netatmo API : {}", e.getMessage(), e);
- } else {
- logger.warn("Unable to connect Netatmo API : {}", e.getMessage());
- }
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- "Netatmo Access Failed, will retry in " + configuration.reconnectInterval + " seconds.");
- }
- // We'll do this every x seconds to guaranty token refresh
- }, 2, configuration.reconnectInterval, TimeUnit.SECONDS);
- }
-
- private void initializeApiClient() {
- try {
- ApiClient apiClient = new ApiClient();
-
- OAuthClientRequest oAuthRequest = OAuthClientRequest.tokenLocation("https://api.netatmo.net/oauth2/token")
- .setClientId(configuration.clientId).setClientSecret(configuration.clientSecret)
- .setUsername(configuration.username).setPassword(configuration.password).setScope(getApiScope())
- .setGrantType(GrantType.PASSWORD).buildBodyMessage();
-
- OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
-
- OAuthJSONAccessTokenResponse accessTokenResponse = oAuthClient.accessToken(oAuthRequest,
- OAuthJSONAccessTokenResponse.class);
- String accessToken = accessTokenResponse.getAccessToken();
-
- for (Authentication authentication : apiClient.getAuthentications().values()) {
- if (authentication instanceof OAuth) {
- ((OAuth) authentication).setAccessToken(accessToken);
- }
- }
-
- apiCreator = new APICreator(apiClient);
- } catch (OAuthSystemException | OAuthProblemException e) {
- throw new RuntimeException("Error on trying to get an access token!", e);
- }
- }
-
- private String getApiScope() {
- List<String> scopes = new ArrayList<>();
-
- if (configuration.readStation) {
- scopes.add("read_station");
- }
-
- if (configuration.readThermostat) {
- scopes.add("read_thermostat");
- scopes.add("write_thermostat");
- }
-
- if (configuration.readHealthyHomeCoach) {
- scopes.add("read_homecoach");
- }
-
- if (configuration.readWelcome) {
- scopes.add("read_camera");
- scopes.add("access_camera");
- scopes.add("write_camera");
- }
-
- if (configuration.readPresence) {
- scopes.add("read_presence");
- scopes.add("access_presence");
- }
-
- return String.join(" ", scopes);
- }
-
- @Override
- public void handleCommand(ChannelUID channelUID, Command command) {
- logger.debug("Netatmo Bridge is read-only and does not handle commands");
- }
-
- public @Nullable PartnerApi getPartnerApi() {
- return apiCreator != null ? apiCreator.getAPI(PartnerApi.class) : null;
- }
-
- public Optional<StationApi> getStationApi() {
- return apiCreator != null ? Optional.of(apiCreator.getAPI(StationApi.class)) : Optional.empty();
- }
-
- public Optional<HealthyhomecoachApi> getHomeCoachApi() {
- return apiCreator != null ? Optional.of(apiCreator.getAPI(HealthyhomecoachApi.class)) : Optional.empty();
- }
-
- public Optional<ThermostatApi> getThermostatApi() {
- return apiCreator != null ? Optional.of(apiCreator.getAPI(ThermostatApi.class)) : Optional.empty();
- }
-
- public Optional<WelcomeApi> getWelcomeApi() {
- return apiCreator != null ? Optional.of(apiCreator.getAPI(WelcomeApi.class)) : Optional.empty();
- }
-
- @Override
- public void dispose() {
- logger.debug("Running dispose()");
-
- WelcomeWebHookServlet servlet = webHookServlet;
- if (servlet != null && getWebHookURI() != null) {
- getWelcomeApi().ifPresent(api -> {
- logger.debug("Releasing Netatmo Welcome WebHook");
- servlet.deactivate();
- api.dropwebhook(WEBHOOK_APP);
- });
- }
-
- ScheduledFuture<?> job = refreshJob;
- if (job != null) {
- job.cancel(true);
- refreshJob = null;
- }
- }
-
- public Optional<NAStationDataBody> getStationsDataBody(@Nullable String equipmentId) {
- Optional<NAStationDataBody> data = getStationApi().map(api -> api.getstationsdata(equipmentId, true).getBody());
- updateStatus(ThingStatus.ONLINE);
- return data;
- }
-
- public List<Float> getStationMeasureResponses(String equipmentId, @Nullable String moduleId, String scale,
- List<String> types) {
- List<NAMeasureBodyElem> data = getStationApi()
- .map(api -> api.getmeasure(equipmentId, scale, types, moduleId, null, "last", 1, true, false).getBody())
- .orElse(null);
- updateStatus(ThingStatus.ONLINE);
- NAMeasureBodyElem element = data != null && !data.isEmpty() ? data.get(0) : null;
- return element != null ? element.getValue().get(0) : Collections.emptyList();
- }
-
- public Optional<NAHealthyHomeCoachDataBody> getHomecoachDataBody(@Nullable String equipmentId) {
- Optional<NAHealthyHomeCoachDataBody> data = getHomeCoachApi()
- .map(api -> api.gethomecoachsdata(equipmentId).getBody());
- updateStatus(ThingStatus.ONLINE);
- return data;
- }
-
- public Optional<NAThermostatDataBody> getThermostatsDataBody(@Nullable String equipmentId) {
- Optional<NAThermostatDataBody> data = getThermostatApi()
- .map(api -> api.getthermostatsdata(equipmentId).getBody());
- updateStatus(ThingStatus.ONLINE);
- return data;
- }
-
- public Optional<NAWelcomeHomeData> getWelcomeDataBody(@Nullable String homeId) {
- Optional<NAWelcomeHomeData> data = getWelcomeApi().map(api -> api.gethomedata(homeId, null).getBody());
- updateStatus(ThingStatus.ONLINE);
- return data;
- }
-
- /**
- * Returns the Url of the picture
- *
- * @return Url of the picture or UnDefType.UNDEF
- */
- public String getPictureUrl(@Nullable String id, @Nullable String key) {
- StringBuilder ret = new StringBuilder();
- if (id != null && key != null) {
- ret.append(WELCOME_PICTURE_URL).append("?").append(WELCOME_PICTURE_IMAGEID).append("=").append(id)
- .append("&").append(WELCOME_PICTURE_KEY).append("=").append(key);
- }
- return ret.toString();
- }
-
- public Optional<AbstractNetatmoThingHandler> findNAThing(@Nullable String searchedId) {
- List<Thing> things = getThing().getThings();
- Stream<AbstractNetatmoThingHandler> naHandlers = things.stream().map(Thing::getHandler)
- .filter(AbstractNetatmoThingHandler.class::isInstance).map(AbstractNetatmoThingHandler.class::cast)
- .filter(handler -> handler.matchesId(searchedId));
- return naHandlers.findAny();
- }
-
- public void webHookEvent(NAWebhookCameraEvent event) {
- // This currently the only known event type but I suspect usage can grow in the future...
- if (event.getAppType() == NAWebhookCameraEvent.AppTypeEnum.CAMERA) {
- Set<AbstractNetatmoThingHandler> modules = new HashSet<>();
- if (WELCOME_EVENTS.contains(event.getEventType()) || PRESENCE_EVENTS.contains(event.getEventType())) {
- String cameraId = event.getCameraId();
- if (cameraId != null) {
- Optional<AbstractNetatmoThingHandler> camera = findNAThing(cameraId);
- camera.ifPresent(modules::add);
- }
- }
- if (HOME_EVENTS.contains(event.getEventType())) {
- String homeId = event.getHomeId();
- if (homeId != null) {
- Optional<AbstractNetatmoThingHandler> home = findNAThing(homeId);
- home.ifPresent(modules::add);
- }
- }
- if (PERSON_EVENTS.contains(event.getEventType())) {
- List<NAWebhookCameraEventPerson> persons = event.getPersons();
- persons.forEach(person -> {
- String personId = person.getId();
- if (personId != null) {
- Optional<AbstractNetatmoThingHandler> personHandler = findNAThing(personId);
- personHandler.ifPresent(modules::add);
- }
- });
- }
- modules.forEach(module -> {
- Channel channel = module.getThing().getChannel(CHANNEL_WELCOME_HOME_EVENT);
- if (channel != null) {
- triggerChannel(channel.getUID(), event.getEventType().toString());
- }
- });
- }
- }
-
- private @Nullable String getWebHookURI() {
- String webHookURI = null;
- WelcomeWebHookServlet webHookServlet = this.webHookServlet;
- if (configuration.webHookUrl != null && (configuration.readWelcome || configuration.readPresence)
- && webHookServlet != null) {
- webHookURI = configuration.webHookUrl + webHookServlet.getPath();
- }
- return webHookURI;
- }
-
- public boolean registerDataListener(NetatmoDataListener dataListener) {
- return dataListeners.add(dataListener);
- }
-
- public boolean unregisterDataListener(NetatmoDataListener dataListener) {
- return dataListeners.remove(dataListener);
- }
-
- public void checkForNewThings(Object data) {
- for (NetatmoDataListener dataListener : dataListeners) {
- dataListener.onDataRefreshed(data);
- }
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 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);
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.handler;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.math.BigDecimal;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
-import org.openhab.binding.netatmo.internal.RefreshStrategy;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.PointType;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.swagger.client.ApiException;
-import io.swagger.client.model.NAPlace;
-
-/**
- * {@link NetatmoDeviceHandler} is the handler for a given
- * device accessed through the Netatmo Bridge
- *
- * @author Gaël L'hopital - Initial contribution
- */
-@NonNullByDefault
-public abstract class NetatmoDeviceHandler<DEVICE> extends AbstractNetatmoThingHandler {
-
- private static final int MIN_REFRESH_INTERVAL = 2000;
- private static final int DEFAULT_REFRESH_INTERVAL = 300000;
-
- private final Logger logger = LoggerFactory.getLogger(NetatmoDeviceHandler.class);
- private final Object updateLock = new Object();
- private @Nullable ScheduledFuture<?> refreshJob;
- private @Nullable RefreshStrategy refreshStrategy;
- private @Nullable DEVICE device;
- protected Map<String, Object> childs = new ConcurrentHashMap<>();
-
- public NetatmoDeviceHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- protected void initializeThing() {
- defineRefreshInterval();
- updateStatus(ThingStatus.ONLINE);
- scheduleRefreshJob();
- }
-
- private void scheduleRefreshJob() {
- RefreshStrategy strategy = refreshStrategy;
- if (strategy == null) {
- return;
- }
- long delay = strategy.nextRunDelayInS();
- logger.debug("Scheduling update channel thread in {} s", delay);
- refreshJob = scheduler.schedule(() -> {
- updateChannels(false);
- ScheduledFuture<?> job = refreshJob;
- if (job != null) {
- job.cancel(false);
- refreshJob = null;
- }
- scheduleRefreshJob();
- }, delay, TimeUnit.SECONDS);
- }
-
- @Override
- public void dispose() {
- logger.debug("Running dispose()");
- ScheduledFuture<?> job = refreshJob;
- if (job != null) {
- job.cancel(true);
- refreshJob = null;
- }
- }
-
- protected abstract Optional<DEVICE> updateReadings();
-
- protected void updateProperties(DEVICE deviceData) {
- }
-
- @Override
- protected void updateChannels() {
- updateChannels(true);
- }
-
- private void updateChannels(boolean requireDefinedRefreshInterval) {
- // Avoid concurrent data readings
- synchronized (updateLock) {
- RefreshStrategy strategy = refreshStrategy;
- if (strategy != null) {
- logger.debug("Data aged of {} s", strategy.dataAge() / 1000);
- boolean dataOutdated = (requireDefinedRefreshInterval && strategy.isSearchingRefreshInterval()) ? false
- : strategy.isDataOutdated();
- if (dataOutdated) {
- logger.debug("Trying to update channels on device {}", getId());
- childs.clear();
-
- Optional<DEVICE> newDeviceReading = Optional.empty();
- try {
- newDeviceReading = updateReadings();
- } catch (ApiException e) {
- if (logger.isDebugEnabled()) {
- // we also attach the stack trace
- logger.error("Unable to connect Netatmo API : {}", e.getMessage(), e);
- } else {
- logger.error("Unable to connect Netatmo API : {}", e.getMessage());
- }
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- "Unable to connect Netatmo API : " + e.getLocalizedMessage());
- }
- if (newDeviceReading.isPresent()) {
- logger.debug("Successfully updated device {} readings! Now updating channels", getId());
- DEVICE theDevice = newDeviceReading.get();
- this.device = theDevice;
- updateStatus(isReachable() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
- updateProperties(theDevice);
- getDataTimestamp().ifPresent(dataTimeStamp -> {
- strategy.setDataTimeStamp(dataTimeStamp, timeZoneProvider.getTimeZone());
- });
- getRadioHelper().ifPresent(helper -> helper.setModule(theDevice));
- getBridgeHandler().ifPresent(handler -> {
- handler.checkForNewThings(theDevice);
- });
- } else {
- logger.debug("Failed to update device {} readings! Skip updating channels", getId());
- }
- // Be sure that all channels for the modules will be updated with refreshed data
- childs.forEach((childId, moduleData) -> {
- findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> {
- naChildModule.setRefreshRequired(true);
- });
- });
- } else {
- logger.debug("Data still valid for device {}", getId());
- }
- super.updateChannels();
- updateChildModules();
- }
- }
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- try {
- Optional<DEVICE> dev = getDevice();
- switch (channelId) {
- case CHANNEL_LAST_STATUS_STORE:
- if (dev.isPresent()) {
- Method getLastStatusStore = dev.get().getClass().getMethod("getLastStatusStore");
- Integer lastStatusStore = (Integer) getLastStatusStore.invoke(dev.get());
- return ChannelTypeUtils.toDateTimeType(lastStatusStore, timeZoneProvider.getTimeZone());
- } else {
- return UnDefType.UNDEF;
- }
- case CHANNEL_LOCATION:
- if (dev.isPresent()) {
- Method getPlace = dev.get().getClass().getMethod("getPlace");
- NAPlace place = (NAPlace) getPlace.invoke(dev.get());
- PointType point = new PointType(new DecimalType(place.getLocation().get(1)),
- new DecimalType(place.getLocation().get(0)));
- if (place.getAltitude() != null) {
- point.setAltitude(new DecimalType(place.getAltitude()));
- }
- return point;
- } else {
- return UnDefType.UNDEF;
- }
- }
- } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
- logger.debug("The device has no method to access {} property ", channelId);
- return UnDefType.NULL;
- }
-
- return super.getNAThingProperty(channelId);
- }
-
- private void updateChildModules() {
- logger.debug("Updating child modules of {}", getId());
- childs.forEach((childId, moduleData) -> {
- findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> {
- logger.debug("Updating child module {}", naChildModule.getId());
- naChildModule.updateChannels(moduleData);
- });
- });
- }
-
- /*
- * Sets the refresh rate of the device depending whether it's a property
- * of the thing or if it's defined by configuration
- */
- private void defineRefreshInterval() {
- BigDecimal dataValidityPeriod;
- if (thing.getProperties().containsKey(PROPERTY_REFRESH_PERIOD)) {
- String refreshPeriodProperty = thing.getProperties().get(PROPERTY_REFRESH_PERIOD);
- if ("auto".equalsIgnoreCase(refreshPeriodProperty)) {
- dataValidityPeriod = new BigDecimal(-1);
- } else {
- dataValidityPeriod = new BigDecimal(refreshPeriodProperty);
- }
- } else {
- Configuration conf = config;
- Object interval = conf != null ? conf.get(REFRESH_INTERVAL) : null;
- if (interval instanceof BigDecimal) {
- dataValidityPeriod = (BigDecimal) interval;
- if (dataValidityPeriod.intValue() < MIN_REFRESH_INTERVAL) {
- logger.info(
- "Refresh interval setting is too small for thing {}, {} ms is considered as refresh interval.",
- thing.getUID(), MIN_REFRESH_INTERVAL);
- dataValidityPeriod = new BigDecimal(MIN_REFRESH_INTERVAL);
- }
- } else {
- dataValidityPeriod = new BigDecimal(DEFAULT_REFRESH_INTERVAL);
- }
- }
- refreshStrategy = new RefreshStrategy(dataValidityPeriod.intValue());
- }
-
- protected abstract Optional<Integer> getDataTimestamp();
-
- public void expireData() {
- RefreshStrategy strategy = refreshStrategy;
- if (strategy != null) {
- strategy.expireData();
- }
- }
-
- protected Optional<DEVICE> getDevice() {
- return Optional.ofNullable(device);
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.handler;
-
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Optional;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * {@link NetatmoModuleHandler} is the handler for a given
- * module device accessed through the Netatmo Device
- *
- * @author Gaël L'hopital - Initial contribution
- */
-@NonNullByDefault
-public class NetatmoModuleHandler<MODULE> extends AbstractNetatmoThingHandler {
- private final Logger logger = LoggerFactory.getLogger(NetatmoModuleHandler.class);
- private @Nullable ScheduledFuture<?> refreshJob;
- private @Nullable MODULE module;
- private boolean refreshRequired;
-
- protected NetatmoModuleHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- protected void initializeThing() {
- refreshJob = scheduler.schedule(() -> {
- requestParentRefresh();
- }, 5, TimeUnit.SECONDS);
- }
-
- @Override
- public void dispose() {
- ScheduledFuture<?> job = refreshJob;
- if (job != null) {
- job.cancel(true);
- refreshJob = null;
- }
- }
-
- protected @Nullable String getParentId() {
- Configuration conf = config;
- Object parentId = conf != null ? conf.get(PARENT_ID) : null;
- if (parentId instanceof String) {
- return ((String) parentId).toLowerCase();
- }
- return null;
- }
-
- public boolean childOf(AbstractNetatmoThingHandler naThingHandler) {
- return naThingHandler.matchesId(getParentId());
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- try {
- Optional<MODULE> mod = getModule();
- if (channelId.equalsIgnoreCase(CHANNEL_LAST_MESSAGE) && mod.isPresent()) {
- Method getLastMessage = mod.get().getClass().getMethod("getLastMessage");
- Integer lastMessage = (Integer) getLastMessage.invoke(mod.get());
- return ChannelTypeUtils.toDateTimeType(lastMessage, timeZoneProvider.getTimeZone());
- }
- } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
- | InvocationTargetException e) {
- logger.debug("The module has no method to access {} property ", channelId);
- return UnDefType.NULL;
- }
-
- return super.getNAThingProperty(channelId);
- }
-
- protected void updateChannels(Object module) {
- MODULE theModule = (MODULE) module;
- setModule(theModule);
- updateStatus(isReachable() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
- getRadioHelper().ifPresent(helper -> helper.setModule(module));
- getBatteryHelper().ifPresent(helper -> helper.setModule(module));
- updateProperties(theModule);
- super.updateChannels();
- }
-
- protected void invalidateParentCacheAndRefresh() {
- setRefreshRequired(true);
- // Leave a bit of time to Netatmo Server to get in sync with new values sent
- scheduler.schedule(() -> {
- invalidateParentCache();
- requestParentRefresh();
- }, 2, TimeUnit.SECONDS);
- }
-
- protected void requestParentRefresh() {
- setRefreshRequired(true);
- findNAThing(getParentId()).ifPresent(AbstractNetatmoThingHandler::updateChannels);
- }
-
- private void invalidateParentCache() {
- findNAThing(getParentId()).map(NetatmoDeviceHandler.class::cast).ifPresent(NetatmoDeviceHandler::expireData);
- }
-
- protected void updateProperties(MODULE moduleData) {
- }
-
- protected boolean isRefreshRequired() {
- return refreshRequired;
- }
-
- protected void setRefreshRequired(boolean refreshRequired) {
- this.refreshRequired = refreshRequired;
- }
-
- protected Optional<MODULE> getModule() {
- return Optional.ofNullable(module);
- }
-
- public void setModule(MODULE module) {
- this.module = module;
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.AircareApi;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link AirCareCapability} give the ability to read home coach api
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class AirCareCapability extends RestCapability<AircareApi> {
+ private final Logger logger = LoggerFactory.getLogger(AirCareCapability.class);
+
+ public AirCareCapability(CommonInterface handler) {
+ super(handler, AircareApi.class);
+ }
+
+ @Override
+ protected List<NAObject> updateReadings(AircareApi api) {
+ try {
+ return List.of(api.getHomeCoach(handler.getId()));
+ } catch (NetatmoException e) {
+ logger.warn("Error retrieving home-coach data '{}' : {}", handler.getId(), e.getMessage());
+ }
+ return List.of();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.AlimentationStatus;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SdCardStatus;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.StateOption;
+
+/**
+ * {@link CameraCapability} give to handle Welcome Camera specifics
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class CameraCapability extends HomeSecurityThingCapability {
+ private final CameraChannelHelper cameraHelper;
+ private final ChannelUID personChannelUID;
+
+ protected @Nullable String localUrl;
+
+ public CameraCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
+ List<ChannelHelper> channelHelpers) {
+ super(handler, descriptionProvider, channelHelpers);
+ this.personChannelUID = new ChannelUID(thing.getUID(), GROUP_LAST_EVENT, CHANNEL_EVENT_PERSON_ID);
+ this.cameraHelper = (CameraChannelHelper) channelHelpers.stream().filter(c -> c instanceof CameraChannelHelper)
+ .findFirst().orElseThrow(() -> new IllegalArgumentException(
+ "CameraCapability must find a CameraChannelHelper, please file a bug report."));
+ }
+
+ @Override
+ public void updateHomeStatusModule(HomeStatusModule newData) {
+ super.updateHomeStatusModule(newData);
+ String vpnUrl = newData.getVpnUrl();
+ if (vpnUrl != null) {
+ localUrl = newData.isLocal() ? securityCapability.map(cap -> cap.ping(vpnUrl)).orElse(null) : null;
+ cameraHelper.setUrls(vpnUrl, localUrl);
+ eventHelper.setUrls(vpnUrl, localUrl);
+ }
+ if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus())
+ || !AlimentationStatus.ALIM_CORRECT_POWER.equals(newData.getAlimStatus())) {
+ statusReason = String.format("%s, %s", newData.getSdStatus(), newData.getAlimStatus());
+ }
+ }
+
+ @Override
+ public void handleCommand(String channelName, Command command) {
+ if (command instanceof OnOffType && CHANNEL_MONITORING.equals(channelName)) {
+ securityCapability.ifPresent(cap -> cap.changeStatus(localUrl, OnOffType.ON.equals(command)));
+ } else {
+ super.handleCommand(channelName, command);
+ }
+ }
+
+ @Override
+ protected void beforeNewData() {
+ super.beforeNewData();
+ homeCapability.ifPresent(cap -> {
+ NAObjectMap<HomeDataPerson> persons = cap.getPersons();
+ descriptionProvider.setStateOptions(personChannelUID, persons.values().stream()
+ .map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList()));
+ });
+ }
+
+ @Override
+ public List<NAObject> updateReadings() {
+ List<NAObject> result = new ArrayList<>();
+ securityCapability.ifPresent(cap -> {
+ Collection<HomeEvent> events = cap.getCameraEvents(handler.getId());
+ if (!events.isEmpty()) {
+ result.add(events.iterator().next());
+ }
+ });
+ return result;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.VENDOR;
+import static org.openhab.core.thing.Thing.*;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.dto.Device;
+import org.openhab.binding.netatmo.internal.api.dto.Event;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.NAMain;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link Capability} is the base class for all inherited capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class Capability {
+
+ protected final Thing thing;
+ protected final CommonInterface handler;
+ protected final ModuleType moduleType;
+
+ protected boolean firstLaunch;
+ protected Map<String, String> properties = Map.of();
+ protected @Nullable String statusReason;
+
+ Capability(CommonInterface handler) {
+ this.handler = handler;
+ this.thing = handler.getThing();
+ this.moduleType = ModuleType.from(thing.getThingTypeUID());
+ }
+
+ public final @Nullable String setNewData(NAObject newData) {
+ beforeNewData();
+ if (newData instanceof HomeData) {
+ updateHomeData((HomeData) newData);
+ }
+ if (newData instanceof HomeStatus) {
+ updateHomeStatus((HomeStatus) newData);
+ }
+ if (newData instanceof HomeStatusModule) {
+ updateHomeStatusModule((HomeStatusModule) newData);
+ }
+ if (newData instanceof Event) {
+ updateEvent((Event) newData);
+ }
+ if (newData instanceof HomeEvent) {
+ updateHomeEvent((HomeEvent) newData);
+ }
+ if (newData instanceof NAThing) {
+ updateNAThing((NAThing) newData);
+ }
+ if (newData instanceof NAMain) {
+ updateNAMain((NAMain) newData);
+ }
+ if (newData instanceof Device) {
+ updateNADevice((Device) newData);
+ }
+ afterNewData(newData);
+ return statusReason;
+ }
+
+ protected void beforeNewData() {
+ properties = new HashMap<>(thing.getProperties());
+ firstLaunch = properties.isEmpty();
+ if (firstLaunch && !moduleType.isLogical()) {
+ properties.put(PROPERTY_VENDOR, VENDOR);
+ properties.put(PROPERTY_MODEL_ID, moduleType.name());
+ }
+ statusReason = null;
+ }
+
+ protected void afterNewData(@Nullable NAObject newData) {
+ if (!properties.equals(thing.getProperties())) {
+ thing.setProperties(properties);
+ }
+ }
+
+ protected void updateNAThing(NAThing newData) {
+ String firmware = newData.getFirmware();
+ if (firmware != null && !firmware.isBlank()) {
+ properties.put(PROPERTY_FIRMWARE_VERSION, firmware);
+ }
+ if (!newData.isReachable()) {
+ statusReason = "@text/device-not-connected";
+ }
+ }
+
+ protected void updateNAMain(NAMain newData) {
+ // do nothing by default, can be overridden by subclasses
+ }
+
+ protected void updateHomeEvent(HomeEvent newData) {
+ // do nothing by default, can be overridden by subclasses
+ }
+
+ protected void updateHomeStatus(HomeStatus newData) {
+ // do nothing by default, can be overridden by subclasses
+ }
+
+ protected void updateHomeData(HomeData newData) {
+ // do nothing by default, can be overridden by subclasses
+ }
+
+ protected void updateEvent(Event newData) {
+ // do nothing by default, can be overridden by subclasses
+ }
+
+ protected void updateNADevice(Device newData) {
+ // do nothing by default, can be overridden by subclasses
+ }
+
+ public void initialize() {
+ // do nothing by default, can be overridden by subclasses
+ }
+
+ public void expireData() {
+ if (!handler.getCapabilities().containsKey(RefreshCapability.class)) {
+ CommonInterface bridgeHandler = handler.getBridgeHandler();
+ if (bridgeHandler != null) {
+ bridgeHandler.expireData();
+ }
+ }
+ }
+
+ public void dispose() {
+ // do nothing by default, can be overridden by subclasses
+ }
+
+ public void updateHomeStatusModule(HomeStatusModule newData) {
+ // do nothing by default, can be overridden by subclasses
+ }
+
+ public void handleCommand(String channelName, Command command) {
+ // do nothing by default, can be overridden by subclasses
+ }
+
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return List.of();
+ }
+
+ public List<NAObject> updateReadings() {
+ return List.of();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * {@link CapabilityMap} is a specialized Map designed to store capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class CapabilityMap extends ConcurrentHashMap<Class<?>, Capability> {
+ private static final long serialVersionUID = -3043492242108419801L;
+
+ public void put(Capability capability) {
+ Class<? extends Capability> clazz = capability.getClass();
+ if (super.get(clazz) == null) {
+ super.put(clazz, capability);
+ }
+ }
+
+ public <T extends Capability> Optional<T> get(Class<T> clazz) {
+ @SuppressWarnings("unchecked")
+ T cap = (T) super.get(clazz);
+ return Optional.ofNullable(cap);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.State;
+
+/**
+ * {@link ChannelHelperCapability} give the capability to dispatch incoming data across the channel helpers.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ChannelHelperCapability extends Capability {
+ private final List<ChannelHelper> channelHelpers;
+
+ public ChannelHelperCapability(CommonInterface handler, List<ChannelHelper> channelHelpers) {
+ super(handler);
+ this.channelHelpers = channelHelpers;
+ }
+
+ @Override
+ public void afterNewData(@Nullable NAObject newData) {
+ super.afterNewData(newData);
+ channelHelpers.forEach(helper -> helper.setNewData(newData));
+ handler.getActiveChannels().forEach(channel -> {
+ ChannelUID channelUID = channel.getUID();
+ String channelID = channelUID.getIdWithoutGroup();
+ String groupId = channelUID.getGroupId();
+ Configuration channelConfig = channel.getConfiguration();
+ for (ChannelHelper helper : channelHelpers) {
+ State state = helper.getChannelState(channelID, groupId, channelConfig);
+ if (state != null) {
+ handler.updateState(channelUID, state);
+ break;
+ }
+ }
+ });
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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";
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import java.time.ZonedDateTime;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.EnergyApi;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataRoom;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.Room;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.StateOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link EnergyCapability} is the base class for handler able to handle energy features
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class EnergyCapability extends RestCapability<EnergyApi> {
+ private final Logger logger = LoggerFactory.getLogger(EnergyCapability.class);
+
+ private int setPointDefaultDuration = -1;
+ private final NetatmoDescriptionProvider descriptionProvider;
+
+ EnergyCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) {
+ super(handler, EnergyApi.class);
+ this.descriptionProvider = descriptionProvider;
+ }
+
+ @Override
+ protected void updateHomeData(HomeData homeData) {
+ NAObjectMap<HomeDataRoom> rooms = homeData.getRooms();
+ NAObjectMap<HomeDataModule> modules = homeData.getModules();
+ handler.getActiveChildren().forEach(handler -> {
+ HomeDataRoom roomData = rooms.get(handler.getId());
+ if (roomData != null) {
+ roomData.setIgnoredForThingUpdate(true);
+ handler.setNewData(roomData);
+ }
+ HomeDataModule moduleData = modules.get(handler.getId());
+ if (moduleData != null) {
+ moduleData.setIgnoredForThingUpdate(true);
+ handler.setNewData(moduleData);
+ }
+ });
+ descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), GROUP_ENERGY, CHANNEL_PLANNING),
+ homeData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName()))
+ .collect(Collectors.toList()));
+ setPointDefaultDuration = homeData.getThermSetpointDefaultDuration();
+ }
+
+ @Override
+ protected void updateHomeStatus(HomeStatus homeStatus) {
+ NAObjectMap<Room> rooms = homeStatus.getRooms();
+ NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
+ handler.getActiveChildren().forEach(handler -> {
+ Room roomData = rooms.get(handler.getId());
+ if (roomData != null) {
+ handler.setNewData(roomData);
+ }
+ HomeStatusModule data = modules.get(handler.getId());
+ if (data != null) {
+ handler.setNewData(data);
+ }
+ });
+ }
+
+ public int getSetpointDefaultDuration() {
+ return setPointDefaultDuration;
+ }
+
+ public void setRoomThermMode(String roomId, SetpointMode targetMode) {
+ getApi().ifPresent(api -> {
+ try {
+ api.setThermpoint(handler.getId(), roomId, targetMode,
+ targetMode == SetpointMode.MAX ? setpointEndTimeFromNow(setPointDefaultDuration) : 0, 0);
+ handler.expireData();
+ } catch (NetatmoException e) {
+ logger.warn("Error setting room thermostat mode '{}' : {}", targetMode, e.getMessage());
+ }
+ });
+ }
+
+ public void setRoomThermTemp(String roomId, double temperature, long endtime, SetpointMode mode) {
+ getApi().ifPresent(api -> {
+ try {
+ api.setThermpoint(handler.getId(), roomId, mode, endtime, temperature);
+ handler.expireData();
+ } catch (NetatmoException e) {
+ logger.warn("Error setting room thermostat mode '{}' : {}", mode, e.getMessage());
+ }
+ });
+ }
+
+ public void setRoomThermTemp(String roomId, double temperature) {
+ setRoomThermTemp(roomId, temperature, setpointEndTimeFromNow(setPointDefaultDuration), SetpointMode.MANUAL);
+ }
+
+ @Override
+ public void handleCommand(String channelName, Command command) {
+ getApi().ifPresent(api -> {
+ try {
+ switch (channelName) {
+ case CHANNEL_PLANNING:
+ api.switchSchedule(handler.getId(), command.toString());
+ break;
+ case CHANNEL_SETPOINT_MODE:
+ SetpointMode targetMode = SetpointMode.valueOf(command.toString());
+ if (targetMode == SetpointMode.MANUAL) {
+ logger.info("Switch to 'Manual' is done by setting a setpoint temp, command ignored");
+ return;
+ }
+ api.setThermMode(handler.getId(), targetMode.apiDescriptor);
+ break;
+ }
+ handler.expireData();
+ } catch (NetatmoException e) {
+ logger.warn("Error handling command '{}' : {}", command, e.getMessage());
+ } catch (IllegalArgumentException e) {
+ logger.warn("Command '{}' sent to channel '{}' is not a valid setpoint mode.", command, channelName);
+ }
+ });
+ }
+
+ private static long setpointEndTimeFromNow(int duration_min) {
+ return ZonedDateTime.now().plusMinutes(duration_min).toEpochSecond();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet;
+
+/**
+ * {@link EventCapability} is the base class for handlers
+ * subject to receive event notifications. This class registers to webhookservlet so
+ * it can be notified when an event arrives.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class EventCapability extends Capability {
+ private Optional<NetatmoServlet> servlet = Optional.empty();
+
+ public EventCapability(CommonInterface handler) {
+ super(handler);
+ }
+
+ @Override
+ public void initialize() {
+ ApiBridgeHandler accountHandler = handler.getAccountHandler();
+ if (accountHandler != null) {
+ servlet = accountHandler.getServlet();
+ servlet.ifPresent(s -> s.registerDataListener(handler.getId(), this));
+ }
+ }
+
+ @Override
+ public void dispose() {
+ servlet.ifPresent(s -> s.unregisterDataListener(this));
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.HomeApi;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
+import org.openhab.binding.netatmo.internal.api.dto.Location;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link HomeCapability} is the base class for handler able to manage persons and modules
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HomeCapability extends RestCapability<HomeApi> {
+ private final Logger logger = LoggerFactory.getLogger(HomeCapability.class);
+
+ private final NetatmoDescriptionProvider descriptionProvider;
+
+ private NAObjectMap<HomeDataPerson> persons = new NAObjectMap<>();
+ private NAObjectMap<HomeDataModule> modules = new NAObjectMap<>();
+
+ private Set<FeatureArea> featuresArea = Set.of();
+
+ public HomeCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) {
+ super(handler, HomeApi.class);
+ this.descriptionProvider = descriptionProvider;
+ }
+
+ @Override
+ protected void updateHomeData(HomeData home) {
+ featuresArea = home.getFeatures();
+ if (hasFeature(FeatureArea.SECURITY) && !handler.getCapabilities().containsKey(SecurityCapability.class)) {
+ handler.getCapabilities().put(new SecurityCapability(handler));
+ }
+ if (hasFeature(FeatureArea.ENERGY) && !handler.getCapabilities().containsKey(EnergyCapability.class)) {
+ handler.getCapabilities().put(new EnergyCapability(handler, descriptionProvider));
+ }
+ if (firstLaunch) {
+ home.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country));
+ home.getTimezone().map(tz -> properties.put(PROPERTY_TIMEZONE, tz));
+ properties.put(GROUP_LOCATION, ((Location) home).getLocation().toString());
+ properties.put(PROPERTY_FEATURE, featuresArea.stream().map(f -> f.name()).collect(Collectors.joining(",")));
+ }
+ }
+
+ @Override
+ protected void afterNewData(@Nullable NAObject newData) {
+ super.afterNewData(newData);
+ if (firstLaunch && !hasFeature(FeatureArea.SECURITY)) {
+ handler.removeChannels(thing.getChannelsOfGroup(GROUP_SECURITY));
+ }
+ if (firstLaunch && !hasFeature(FeatureArea.ENERGY)) {
+ handler.removeChannels(thing.getChannelsOfGroup(GROUP_ENERGY));
+ }
+ }
+
+ private boolean hasFeature(FeatureArea seeked) {
+ return featuresArea.contains(seeked);
+ }
+
+ public NAObjectMap<HomeDataPerson> getPersons() {
+ return persons;
+ }
+
+ public NAObjectMap<HomeDataModule> getModules() {
+ return modules;
+ }
+
+ @Override
+ protected List<NAObject> updateReadings(HomeApi api) {
+ List<NAObject> result = new ArrayList<>();
+ try {
+ HomeData homeData = api.getHomeData(handler.getId());
+ if (homeData != null) {
+ result.add(homeData);
+ persons = homeData.getPersons();
+ modules = homeData.getModules();
+ }
+ HomeStatus homeStatus = api.getHomeStatus(handler.getId());
+ if (homeStatus != null) {
+ result.add(homeStatus);
+ }
+ } catch (NetatmoException e) {
+ logger.warn("Error gettting Home informations : {}", e.getMessage());
+ }
+ return result;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.EventChannelHelper;
+import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+
+/**
+ * {@link HomeSecurityThingCapability} is the ancestor of capabilities hosted by a security home
+ * e.g. person and camera capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HomeSecurityThingCapability extends Capability {
+ protected final NetatmoDescriptionProvider descriptionProvider;
+ protected final EventChannelHelper eventHelper;
+
+ protected Optional<SecurityCapability> securityCapability = Optional.empty();
+ protected Optional<HomeCapability> homeCapability = Optional.empty();
+
+ public HomeSecurityThingCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
+ List<ChannelHelper> channelHelpers) {
+ super(handler);
+ this.descriptionProvider = descriptionProvider;
+ this.eventHelper = (EventChannelHelper) channelHelpers.stream().filter(c -> c instanceof EventChannelHelper)
+ .findFirst().orElseThrow(() -> new IllegalArgumentException(
+ "HomeSecurityThingCapability must find an EventChannelHelper, please file a bug report."));
+ eventHelper.setModuleType(moduleType);
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ securityCapability = handler.getHomeCapability(SecurityCapability.class);
+ homeCapability = handler.getHomeCapability(HomeCapability.class);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
+
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.WeatherApi;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.config.MeasureConfiguration;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.MeasuresChannelHelper;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link MeasureCapability} is the base class for handler able to handle user defined measures
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class MeasureCapability extends RestCapability<WeatherApi> {
+ private final Logger logger = LoggerFactory.getLogger(MeasureCapability.class);
+ private final Map<String, State> measures = new HashMap<>();
+
+ public MeasureCapability(CommonInterface handler, List<ChannelHelper> helpers) {
+ super(handler, WeatherApi.class);
+ MeasuresChannelHelper measureChannelHelper = (MeasuresChannelHelper) helpers.stream()
+ .filter(c -> c instanceof MeasuresChannelHelper).findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(
+ "MeasureCapability must find a MeasuresChannelHelper, please file a bug report."));
+ measureChannelHelper.setMeasures(measures);
+ }
+
+ @Override
+ public List<NAObject> updateReadings(WeatherApi api) {
+ String bridgeId = handler.getBridgeId();
+ String deviceId = bridgeId != null ? bridgeId : handler.getId();
+ String moduleId = bridgeId != null ? handler.getId() : null;
+ updateMeasures(api, deviceId, moduleId);
+ return List.of();
+ }
+
+ private void updateMeasures(WeatherApi api, String deviceId, @Nullable String moduleId) {
+ measures.clear();
+ handler.getActiveChannels().filter(channel -> !channel.getConfiguration().getProperties().isEmpty())
+ .forEach(channel -> {
+ ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
+ if (channelTypeUID != null) {
+ MeasureConfiguration measureDef = channel.getConfiguration().as(MeasureConfiguration.class);
+ String descriptor = channelTypeUID.getId().split("-")[0];
+ try {
+ Object result = measureDef.limit.isBlank()
+ ? api.getMeasures(deviceId, moduleId, measureDef.period, descriptor)
+ : api.getMeasures(deviceId, moduleId, measureDef.period, descriptor,
+ measureDef.limit);
+ MeasureClass.AS_SET.stream().filter(mc -> mc.apiDescriptor.equals(descriptor)).findFirst()
+ .ifPresent(mc -> {
+ State state = result instanceof ZonedDateTime
+ ? toDateTimeType((ZonedDateTime) result)
+ : result instanceof Double ? toQuantityType((Double) result, mc)
+ : UnDefType.UNDEF;
+ measures.put(channel.getUID().getIdWithoutGroup(), state);
+ });
+ } catch (NetatmoException e) {
+ logger.warn("Error getting measures for channel {}, check configuration",
+ channel.getLabel());
+ }
+ }
+ });
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.EventType;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.binding.netatmo.internal.api.dto.Event;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.StateOption;
+
+/**
+ * {@link PersonCapability} gives the ability to handle Person specifics
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class PersonCapability extends HomeSecurityThingCapability {
+ private final ChannelUID cameraChannelUID;
+ private @Nullable ZonedDateTime lastEventTime;
+
+ public PersonCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
+ List<ChannelHelper> channelHelpers) {
+ super(handler, descriptionProvider, channelHelpers);
+ this.cameraChannelUID = new ChannelUID(thing.getUID(), GROUP_PERSON_EVENT, CHANNEL_EVENT_CAMERA_ID);
+ }
+
+ @Override
+ protected void beforeNewData() {
+ super.beforeNewData();
+ homeCapability.ifPresent(cap -> {
+ Stream<HomeDataModule> cameras = cap.getModules().values().stream()
+ .filter(module -> module.getType() == ModuleType.WELCOME);
+ descriptionProvider.setStateOptions(cameraChannelUID,
+ cameras.map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList()));
+ });
+ }
+
+ @Override
+ public void handleCommand(String channelName, Command command) {
+ if ((command instanceof OnOffType) && CHANNEL_PERSON_AT_HOME.equals(channelName)) {
+ securityCapability.ifPresent(cap -> cap.setPersonAway(handler.getId(), OnOffType.OFF.equals(command)));
+ }
+ }
+
+ @Override
+ public void updateEvent(Event event) {
+ super.updateEvent(event);
+ EventType eventType = event.getEventType();
+ ZonedDateTime localLast = lastEventTime;
+ ZonedDateTime eventTime = event.getTime();
+ if ((localLast != null && !eventTime.isAfter(localLast)) || !eventType.appliesOn(ModuleType.PERSON)) {
+ return; // ignore incoming events if they are deprecated
+ }
+ lastEventTime = eventTime;
+ handler.triggerChannel(CHANNEL_HOME_EVENT,
+ event.getSubTypeDescription().map(st -> st.name()).orElse(event.getEventType().name()));
+ }
+
+ @Override
+ public List<NAObject> updateReadings() {
+ List<NAObject> result = new ArrayList<>();
+ securityCapability.ifPresent(cap -> {
+ Collection<HomeEvent> events = cap.getPersonEvents(handler.getId());
+ if (!events.isEmpty()) {
+ result.add(events.iterator().next());
+ }
+ });
+ return result;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.CHANNEL_FLOODLIGHT;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
+import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link PresenceCapability} give to handle Presence Camera specifics
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class PresenceCapability extends CameraCapability {
+ private final Logger logger = LoggerFactory.getLogger(PresenceCapability.class);
+
+ public PresenceCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
+ List<ChannelHelper> channelHelpers) {
+ super(handler, descriptionProvider, channelHelpers);
+ }
+
+ @Override
+ public void handleCommand(String channelName, Command command) {
+ if (CHANNEL_FLOODLIGHT.equals(channelName)) {
+ if (command instanceof OnOffType) {
+ changeFloodlightMode(command == OnOffType.ON ? FloodLightMode.ON : FloodLightMode.OFF);
+ return;
+ } else if (command instanceof StringType) {
+ try {
+ FloodLightMode mode = FloodLightMode.valueOf(command.toString());
+ changeFloodlightMode(mode);
+ } catch (IllegalArgumentException e) {
+ logger.info("Incorrect command '{}' received for channel '{}'", command, channelName);
+ }
+ return;
+ }
+ }
+ super.handleCommand(channelName, command);
+ }
+
+ private void changeFloodlightMode(FloodLightMode mode) {
+ securityCapability.ifPresent(cap -> cap.changeFloodlightMode(localUrl, mode));
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import static java.time.temporal.ChronoUnit.*;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Optional;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.core.thing.ThingStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link RefreshCapability} is the class used to embed the refreshing needs calculation for devices
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class RefreshCapability extends Capability {
+ private static final Duration DEFAULT_DELAY = Duration.of(20, SECONDS);
+ private static final Duration PROBING_INTERVAL = Duration.of(120, SECONDS);
+ private static final Duration OFFLINE_INTERVAL = Duration.of(15, MINUTES);
+
+ private final Logger logger = LoggerFactory.getLogger(RefreshCapability.class);
+ private final ScheduledExecutorService scheduler;
+
+ private Duration dataValidity;
+ private Instant dataTimeStamp = Instant.now();
+ private Instant dataTimeStamp0 = Instant.MIN;
+ private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
+ private final boolean refreshConfigured;
+
+ public RefreshCapability(CommonInterface handler, ScheduledExecutorService scheduler, int refreshInterval) {
+ super(handler);
+ this.scheduler = scheduler;
+ this.dataValidity = Duration.ofSeconds(Math.max(0, refreshInterval));
+ this.refreshConfigured = !probing();
+ freeJobAndReschedule(2);
+ }
+
+ @Override
+ public void dispose() {
+ freeJobAndReschedule(0);
+ super.dispose();
+ }
+
+ @Override
+ public void expireData() {
+ dataTimeStamp = Instant.now().minus(dataValidity);
+ freeJobAndReschedule(1);
+ }
+
+ private Duration dataAge() {
+ return Duration.between(dataTimeStamp, Instant.now());
+ }
+
+ private boolean probing() {
+ return dataValidity.getSeconds() <= 0;
+ }
+
+ private void proceedWithUpdate() {
+ handler.proceedWithUpdate();
+ long delay;
+ if (!ThingStatus.ONLINE.equals(handler.getThing().getStatus())) {
+ logger.debug("Module is not ONLINE; special refresh interval is used");
+ delay = OFFLINE_INTERVAL.toSeconds();
+ if (probing()) {
+ dataTimeStamp0 = Instant.MIN;
+ }
+ } else if (refreshConfigured) {
+ delay = dataValidity.getSeconds();
+ } else {
+ delay = (probing() ? PROBING_INTERVAL : dataValidity.minus(dataAge()).plus(DEFAULT_DELAY)).toSeconds();
+ }
+ delay = delay < 2 ? PROBING_INTERVAL.toSeconds() : delay;
+ logger.debug("Module refreshed, next one in {} s", delay);
+ freeJobAndReschedule(delay);
+ }
+
+ @Override
+ protected void updateNAThing(NAThing newData) {
+ super.updateNAThing(newData);
+ newData.getLastSeen().ifPresent(timestamp -> {
+ Instant tsInstant = timestamp.toInstant();
+ if (probing()) {
+ if (Instant.MIN.equals(dataTimeStamp0)) {
+ dataTimeStamp0 = tsInstant;
+ logger.debug("First data timestamp is {}", dataTimeStamp0);
+ } else if (tsInstant.isAfter(dataTimeStamp0)) {
+ dataValidity = Duration.between(dataTimeStamp0, tsInstant);
+ logger.debug("Data validity period identified to be {}", dataValidity);
+ } else {
+ logger.debug("Data validity period not yet found - data timestamp unchanged");
+ }
+ }
+ dataTimeStamp = tsInstant;
+ });
+ }
+
+ private void freeJobAndReschedule(long delay) {
+ refreshJob.ifPresent(job -> job.cancel(false));
+ refreshJob = delay == 0 ? Optional.empty()
+ : Optional.of(scheduler.schedule(() -> proceedWithUpdate(), delay, TimeUnit.SECONDS));
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.RestManager;
+import org.openhab.binding.netatmo.internal.api.dto.Device;
+import org.openhab.binding.netatmo.internal.api.dto.Module;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+
+/**
+ * The {@link RestCapability} is the base class for handler capabilities
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public abstract class RestCapability<T extends RestManager> extends DeviceCapability {
+ private Optional<T> api = Optional.empty();
+ private Class<T> restManagerClass;
+
+ RestCapability(CommonInterface handler, Class<T> restManagerClazz) {
+ super(handler);
+ this.restManagerClass = restManagerClazz;
+ }
+
+ @Override
+ protected void updateNADevice(Device newData) {
+ super.updateNADevice(newData);
+ NAObjectMap<Module> modules = newData.getModules();
+ handler.getActiveChildren().forEach(child -> {
+ Module childData = modules.get(child.getId());
+ if (childData != null) {
+ child.setNewData(childData);
+ }
+ });
+ }
+
+ @Override
+ public final List<NAObject> updateReadings() {
+ List<NAObject> result = new ArrayList<>();
+ getApi().ifPresent(api -> result.addAll(updateReadings(api)));
+ return result;
+ }
+
+ protected List<NAObject> updateReadings(T api) {
+ return List.of();
+ }
+
+ protected Optional<T> getApi() {
+ if (api.isEmpty()) {
+ ApiBridgeHandler bridgeApi = handler.getAccountHandler();
+ if (bridgeApi != null) {
+ api = Optional.ofNullable(bridgeApi.getRestManager(restManagerClass));
+ }
+ }
+ return api;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.commandToQuantity;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.action.RoomActions;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link RoomCapability} gives the ability to handle Room specifics
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class RoomCapability extends Capability {
+ private final Logger logger = LoggerFactory.getLogger(RoomCapability.class);
+ private Optional<EnergyCapability> energyCapability = Optional.empty();
+
+ public RoomCapability(CommonInterface handler) {
+ super(handler);
+ }
+
+ @Override
+ public void initialize() {
+ energyCapability = handler.getHomeCapability(EnergyCapability.class);
+ }
+
+ @Override
+ public void handleCommand(String channelName, Command command) {
+ if (CHANNEL_SETPOINT_MODE.equals(channelName)) {
+ try {
+ SetpointMode targetMode = SetpointMode.valueOf(command.toString());
+ if (targetMode == SetpointMode.MANUAL) {
+ logger.info("Switch to 'Manual' mode is done by setting a setpoint temp, command ignored");
+ } else {
+ energyCapability.ifPresent(cap -> cap.setRoomThermMode(handler.getId(), targetMode));
+ }
+ } catch (IllegalArgumentException e) {
+ logger.info("Command '{}' is not a valid setpoint mode for channel '{}'", command, channelName);
+ }
+ } else if (CHANNEL_VALUE.equals(channelName)) {
+ QuantityType<?> quantity = commandToQuantity(command, MeasureClass.INSIDE_TEMPERATURE);
+ if (quantity != null) {
+ energyCapability.ifPresent(cap -> cap.setRoomThermTemp(handler.getId(), quantity.doubleValue()));
+ } else {
+ logger.warn("Incorrect command '{}' on channel '{}'", command, channelName);
+ }
+ }
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return List.of(RoomActions.class);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.SecurityApi;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
+import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
+import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link SecurityCapability} is the base class for handler able to handle security features
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+class SecurityCapability extends RestCapability<SecurityApi> {
+ private final Logger logger = LoggerFactory.getLogger(SecurityCapability.class);
+
+ SecurityCapability(CommonInterface handler) {
+ super(handler, SecurityApi.class);
+ }
+
+ @Override
+ protected void updateHomeData(HomeData homeData) {
+ NAObjectMap<HomeDataPerson> persons = homeData.getPersons();
+ NAObjectMap<HomeDataModule> modules = homeData.getModules();
+ handler.getActiveChildren().forEach(childHandler -> {
+ String childId = childHandler.getId();
+ persons.getOpt(childId).ifPresentOrElse(person -> {
+ person.setIgnoredForThingUpdate(true);
+ childHandler.setNewData(person);
+ }, () -> {
+ modules.getOpt(childId).ifPresent(module -> {
+ module.setIgnoredForThingUpdate(true);
+ childHandler.setNewData(module);
+ });
+ });
+ });
+ }
+
+ @Override
+ protected void updateHomeStatus(HomeStatus homeStatus) {
+ NAObjectMap<HomeStatusPerson> persons = homeStatus.getPersons();
+ NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
+ handler.getActiveChildren().forEach(childHandler -> {
+ String childId = childHandler.getId();
+ persons.getOpt(childId).ifPresentOrElse(person -> childHandler.setNewData(person), () -> {
+ modules.getOpt(childId).ifPresentOrElse(module -> childHandler.setNewData(module), () -> {
+ // This module is not present in the homestatus data, so it is considered as unreachable
+ HomeStatusModule module = new HomeStatusModule();
+ module.setReachable(false);
+ childHandler.setNewData(module);
+ });
+ });
+ });
+ }
+
+ @Override
+ protected void updateHomeEvent(HomeEvent homeEvent) {
+ String personId = homeEvent.getPersonId();
+ if (personId != null) {
+ handler.getActiveChildren().stream().filter(handler -> personId.equals(handler.getId())).findFirst()
+ .ifPresent(handler -> {
+ homeEvent.setIgnoredForThingUpdate(true);
+ handler.setNewData(homeEvent);
+ });
+ }
+ String cameraId = homeEvent.getCameraId();
+ handler.getActiveChildren().stream().filter(handler -> cameraId.equals(handler.getId())).findFirst()
+ .ifPresent(handler -> {
+ homeEvent.setIgnoredForThingUpdate(true);
+ handler.setNewData(homeEvent);
+ });
+ }
+
+ public Collection<HomeEvent> getCameraEvents(String cameraId) {
+ return getApi().map(api -> {
+ try {
+ return api.getCameraEvents(handler.getId(), cameraId);
+ } catch (NetatmoException e) {
+ logger.warn("Error retrieving last events of camera '{}' : {}", cameraId, e.getMessage());
+ return null;
+ }
+ }).orElse(List.of());
+ }
+
+ public Collection<HomeEvent> getPersonEvents(String personId) {
+ return getApi().map(api -> {
+ try {
+ return api.getPersonEvents(handler.getId(), personId);
+ } catch (NetatmoException e) {
+ logger.warn("Error retrieving last events of person '{}' : {}", personId, e.getMessage());
+ return null;
+ }
+ }).orElse(List.of());
+ }
+
+ public void setPersonAway(String personId, boolean away) {
+ getApi().ifPresent(api -> {
+ try {
+ api.setPersonAwayStatus(handler.getId(), personId, away);
+ handler.expireData();
+ } catch (NetatmoException e) {
+ logger.warn("Error setting person away/at home '{}' : {}", personId, e.getMessage());
+ }
+ });
+ }
+
+ public @Nullable String ping(String vpnUrl) {
+ return getApi().map(api -> {
+ try {
+ return api.ping(vpnUrl);
+ } catch (NetatmoException e) {
+ logger.warn("Error pinging camera '{}' : {}", vpnUrl, e.getMessage());
+ return null;
+ }
+ }).orElse(null);
+ }
+
+ public void changeStatus(@Nullable String localURL, boolean status) {
+ if (localURL == null) {
+ logger.info("Monitoring changes can only be done on local camera.");
+ return;
+ }
+ getApi().ifPresent(api -> {
+ try {
+ api.changeStatus(localURL, status);
+ handler.expireData();
+ } catch (NetatmoException e) {
+ logger.warn("Error changing camera monitoring status '{}' : {}", status, e.getMessage());
+ }
+ });
+ }
+
+ public void changeFloodlightMode(@Nullable String localURL, FloodLightMode mode) {
+ if (localURL == null) {
+ logger.info("Changing floodlight mode can only be done on local camera.");
+ return;
+ }
+ getApi().ifPresent(api -> {
+ try {
+ api.changeFloodLightMode(localURL, mode);
+ handler.expireData();
+ } catch (NetatmoException e) {
+ logger.warn("Error changing Presence floodlight mode '{}' : {}", mode, e.getMessage());
+ }
+ });
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.capability;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.WeatherApi;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.handler.CommonInterface;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link WeatherCapability} give the ability to read weather station api
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WeatherCapability extends RestCapability<WeatherApi> {
+ private final Logger logger = LoggerFactory.getLogger(WeatherCapability.class);
+
+ public WeatherCapability(CommonInterface handler) {
+ super(handler, WeatherApi.class);
+ }
+
+ @Override
+ protected List<NAObject> updateReadings(WeatherApi api) {
+ try {
+ return List.of(api.getStationData(handler.getId()));
+ } catch (NetatmoException e) {
+ logger.warn("Error retrieving weather data '{}' : {}", handler.getId(), e.getMessage());
+ }
+ return List.of();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.channelhelper;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.binding.netatmo.internal.api.dto.Dashboard;
+import org.openhab.binding.netatmo.internal.api.dto.Event;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.binding.netatmo.internal.providers.NetatmoThingTypeProvider;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link ChannelHelper} is the base class for all channel helpers.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public abstract class ChannelHelper {
+ private @Nullable NAObject data;
+ private final Set<String> channelGroupTypes;
+ private final Set<String> channelGroups = new HashSet<>();
+ private Set<String> extensibleChannels = Set.of();
+
+ ChannelHelper(String... providedGroups) {
+ this.channelGroupTypes = Set.of(providedGroups);
+ channelGroupTypes.forEach(groupType -> channelGroups.add(NetatmoThingTypeProvider.toGroupName(groupType)));
+ }
+
+ ChannelHelper(String providedGroup, MeasureClass measureClass) {
+ this(providedGroup);
+ this.extensibleChannels = measureClass.channels.keySet();
+ }
+
+ public void setNewData(@Nullable NAObject data) {
+ this.data = data;
+ }
+
+ public final @Nullable State getChannelState(String channelId, @Nullable String groupId, Configuration config) {
+ State result = null;
+ if (channelGroups.isEmpty() || (groupId != null && channelGroups.contains(groupId))) {
+ NAObject localData = data;
+ if (localData instanceof Event) {
+ result = internalGetEvent(channelId, (Event) localData);
+ if (result != null) {
+ return result;
+ }
+ }
+ if (localData instanceof NAThing) {
+ NAThing naThing = (NAThing) localData;
+ result = internalGetProperty(channelId, naThing, config);
+ if (result != null) {
+ return result;
+ }
+ Dashboard dashboard = naThing.getDashboardData();
+ if (dashboard != null) {
+ result = internalGetDashboard(channelId, dashboard);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ if (localData instanceof NAObject) {
+ result = internalGetObject(channelId, localData);
+ if (result != null) {
+ return result;
+ }
+ }
+ result = internalGetOther(channelId);
+ }
+ return result;
+ }
+
+ protected @Nullable State internalGetObject(String channelId, NAObject localData) {
+ return null;
+ }
+
+ protected @Nullable State internalGetOther(String channelId) {
+ return null;
+ }
+
+ protected @Nullable State internalGetDashboard(String channelId, Dashboard dashboard) {
+ return null;
+ }
+
+ protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+ return null;
+ }
+
+ protected @Nullable State internalGetEvent(String channelId, Event event) {
+ return null;
+ }
+
+ public Set<String> getChannelGroupTypes() {
+ return channelGroupTypes;
+ }
+
+ public Set<String> getExtensibleChannels() {
+ return extensibleChannels;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
+
+import java.time.DayOfWeek;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.binding.netatmo.internal.api.dto.ThermProgram;
+import org.openhab.binding.netatmo.internal.api.dto.TimeTableItem;
+import org.openhab.binding.netatmo.internal.api.dto.Zone;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link HomeEnergyChannelHelper} handles specific channels of thermostat settings at home level.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HomeEnergyChannelHelper extends ChannelHelper {
+
+ public HomeEnergyChannelHelper() {
+ super(GROUP_ENERGY);
+ }
+
+ @Override
+ protected @Nullable State internalGetProperty(String channelId, NAThing data, Configuration config) {
+ if (data instanceof HomeData) {
+ HomeData homeData = (HomeData) data;
+ SetpointMode thermMode = homeData.getThermMode();
+ ThermProgram currentProgram = homeData.getActiveProgram();
+ switch (channelId) {
+ case CHANNEL_SETPOINT_DURATION:
+ return toQuantityType(homeData.getThermSetpointDefaultDuration(), Units.MINUTE);
+ case CHANNEL_PLANNING:
+ return (currentProgram != null ? toStringType(currentProgram.getName()) : null);
+ case CHANNEL_SETPOINT_END_TIME:
+ switch (thermMode) {
+ case PROGRAM:
+ case HOME:
+ case SCHEDULE:
+ return currentProgram != null ? toDateTimeType(nextProgramTime(currentProgram))
+ : UnDefType.UNDEF;
+ default:
+ return UnDefType.UNDEF;
+ }
+ case CHANNEL_SETPOINT_MODE:
+ switch (thermMode) {
+ case OFF:
+ case MAX:
+ case UNKNOWN:
+ return UnDefType.UNDEF;
+ case PROGRAM:
+ case HOME:
+ case SCHEDULE:
+ if (currentProgram != null) {
+ TimeTableItem currentProgramMode = currentProgramMode(currentProgram);
+ if (currentProgramMode != null) {
+ Zone zone = currentProgram.getZone(String.valueOf(currentProgramMode.getZoneId()));
+ if (zone != null) {
+ return new StringType(zone.getName());
+ }
+ }
+ }
+ return UnDefType.NULL;
+ default:
+ return toStringType(thermMode);
+ }
+ }
+ }
+ return null;
+ }
+
+ private static ZonedDateTime programBaseTimeZdt() {
+ return ZonedDateTime.now().with(DayOfWeek.MONDAY).truncatedTo(ChronoUnit.DAYS);
+ }
+
+ private static long minutesSinceProgramBaseTime() {
+ return ChronoUnit.MINUTES.between(programBaseTimeZdt(), ZonedDateTime.now());
+ }
+
+ private static @Nullable TimeTableItem currentProgramMode(ThermProgram activeProgram) {
+ long diff = minutesSinceProgramBaseTime();
+ return activeProgram.getTimetable().stream().filter(t -> t.getMinuteOffset() < diff)
+ .reduce((first, second) -> second).orElse(null);
+ }
+
+ private static ZonedDateTime nextProgramTime(ThermProgram activeProgram) {
+ long diff = minutesSinceProgramBaseTime();
+ // By default we'll use the first slot of next week - this case will be true if
+ // we are in the last schedule of the week so below loop will not exit by break
+ List<TimeTableItem> timetable = activeProgram.getTimetable();
+ int next = timetable.get(0).getMinuteOffset() + (7 * 24 * 60);
+ for (TimeTableItem timeTable : timetable) {
+ if (timeTable.getMinuteOffset() > diff) {
+ next = timeTable.getMinuteOffset();
+ break;
+ }
+ }
+ return programBaseTimeZdt().plusMinutes(next);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toRawType;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.HomeData;
+import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson;
+import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
+import org.openhab.binding.netatmo.internal.api.dto.NAObject;
+import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link HomeSecurityChannelHelper} handles specific information for security purpose.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class HomeSecurityChannelHelper extends ChannelHelper {
+ private long persons = -1;
+ private long unknowns = -1;
+ private @Nullable String unknownSnapshot;
+ private List<String> knownIds = List.of();
+
+ public HomeSecurityChannelHelper() {
+ super(GROUP_SECURITY);
+ }
+
+ @Override
+ public void setNewData(@Nullable NAObject data) {
+ super.setNewData(data);
+ if (data instanceof HomeData) {
+ HomeData homeData = (HomeData) data;
+ knownIds = homeData.getPersons().values().stream().filter(person -> person.isKnown()).map(p -> p.getId())
+ .collect(Collectors.toList());
+ }
+ if (data instanceof HomeStatus) {
+ HomeStatus status = (HomeStatus) data;
+ NAObjectMap<HomeStatusPerson> allPersons = status.getPersons();
+ List<HomeStatusPerson> present = allPersons.values().stream().filter(p -> !p.isOutOfSight())
+ .collect(Collectors.toList());
+
+ persons = present.size();
+ unknowns = present.stream().filter(person -> !knownIds.contains(person.getId())).count();
+ }
+ }
+
+ @Override
+ protected @Nullable State internalGetOther(String channelId) {
+ switch (channelId) {
+ case CHANNEL_PERSON_COUNT:
+ return persons != -1 ? new DecimalType(persons) : UnDefType.NULL;
+ case CHANNEL_UNKNOWN_PERSON_COUNT:
+ return unknowns != -1 ? new DecimalType(unknowns) : UnDefType.NULL;
+ case CHANNEL_UNKNOWN_PERSON_PICTURE:
+ return unknownSnapshot != null ? toRawType(unknownSnapshot) : UnDefType.NULL;
+ }
+ return null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.channelhelper;
+
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link MeasuresChannelHelper} handles extensible channels based on getMeasure endpoint.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@NonNullByDefault
+public class MeasuresChannelHelper extends ChannelHelper {
+ private @Nullable Map<String, State> measures;
+
+ public void setMeasures(Map<String, State> measures) {
+ this.measures = measures;
+ }
+
+ @Override
+ protected @Nullable State internalGetOther(String channelId) {
+ Map<String, State> localMeasures = measures;
+ if (localMeasures != null) {
+ return localMeasures.get(channelId);
+ }
+ throw new IllegalArgumentException("localMeasures should not be null, please file a bug report.");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.handler.channelhelper;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toDateTimeType;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.dto.NAThing;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link TimestampChannelHelper} handles specific behavior
+ * of modules reporting last seen
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TimestampChannelHelper extends ChannelHelper {
+
+ public TimestampChannelHelper() {
+ this(GROUP_TIMESTAMP);
+ }
+
+ protected TimestampChannelHelper(String groupName) {
+ super(groupName);
+ }
+
+ @Override
+ protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
+ Optional<ZonedDateTime> lastSeen = naThing.getLastSeen();
+ return CHANNEL_LAST_SEEN.equals(channelId) && lastSeen.isPresent() ? toDateTimeType(lastSeen) : null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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;
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.homecoach;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAHealthyHomeCoach;
-
-/**
- * {@link NAHealthyHomeCoachHandler} is the class used to handle the Health Home Coach device
- *
- * @author Michael Svinth - Initial contribution OH2 version
- *
- */
-@NonNullByDefault
-public class NAHealthyHomeCoachHandler extends NetatmoDeviceHandler<NAHealthyHomeCoach> {
-
- public NAHealthyHomeCoachHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- protected Optional<NAHealthyHomeCoach> updateReadings() {
- return getBridgeHandler().flatMap(handler -> handler.getHomecoachDataBody(getId()))
- .map(dataBody -> nonNullStream(dataBody.getDevices())
- .filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
- }
-
- @Override
- protected void updateProperties(NAHealthyHomeCoach deviceData) {
- updateProperties(deviceData.getFirmware(), deviceData.getType());
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- NADashboardData dashboardData = getDevice().map(d -> d.getDashboardData()).orElse(null);
- if (dashboardData != null) {
- switch (channelId) {
- case CHANNEL_CO2:
- return toQuantityType(dashboardData.getCo2(), API_CO2_UNIT);
- case CHANNEL_TEMPERATURE:
- return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
- case CHANNEL_HEALTH_INDEX:
- return toStringType(toHealthIndexString(dashboardData.getHealthIdx()));
- case CHANNEL_MIN_TEMP:
- return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
- case CHANNEL_MAX_TEMP:
- return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
- case CHANNEL_TEMP_TREND:
- return toStringType(dashboardData.getTempTrend());
- case CHANNEL_NOISE:
- return toQuantityType(dashboardData.getNoise(), API_NOISE_UNIT);
- case CHANNEL_PRESSURE:
- return toQuantityType(dashboardData.getPressure(), API_PRESSURE_UNIT);
- case CHANNEL_PRESS_TREND:
- return toStringType(dashboardData.getPressureTrend());
- case CHANNEL_ABSOLUTE_PRESSURE:
- return toQuantityType(dashboardData.getAbsolutePressure(), API_PRESSURE_UNIT);
- case CHANNEL_TIMEUTC:
- return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
- case CHANNEL_DATE_MIN_TEMP:
- return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
- case CHANNEL_DATE_MAX_TEMP:
- return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
- case CHANNEL_HUMIDITY:
- return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
- }
- }
- return super.getNAThingProperty(channelId);
- }
-
- private @Nullable String toHealthIndexString(@Nullable Integer healthIndex) {
- if (healthIndex == null) {
- return null;
- }
- switch (healthIndex) {
- case 0:
- return "healthy";
- case 1:
- return "fine";
- case 2:
- return "fair";
- case 3:
- return "poor";
- case 4:
- return "unhealthy";
- default:
- return healthIndex.toString();
- }
- }
-
- @Override
- protected Optional<Integer> getDataTimestamp() {
- return getDevice().map(d -> d.getLastStatusStore());
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.presence;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.toOnOffType;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.camera.CameraHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-import io.swagger.client.model.NAWelcomeCamera;
-
-/**
- * {@link NAPresenceCameraHandler} is the class used to handle Presence camera data
- *
- * @author Sven Strohschein - Initial contribution
- */
-@NonNullByDefault
-public class NAPresenceCameraHandler extends CameraHandler {
-
- private static final String FLOODLIGHT_SET_URL_PATH = "/command/floodlight_set_config";
-
- private State floodlightAutoModeState = UnDefType.UNDEF;
-
- public NAPresenceCameraHandler(final Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- public void handleCommand(ChannelUID channelUID, Command command) {
- String channelId = channelUID.getId();
- switch (channelId) {
- case CHANNEL_CAMERA_FLOODLIGHT:
- if (command == OnOffType.ON) {
- switchFloodlight(true);
- } else if (command == OnOffType.OFF) {
- switchFloodlight(false);
- }
- break;
- case CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE:
- if (command == OnOffType.ON) {
- switchFloodlightAutoMode(true);
- } else if (command == OnOffType.OFF) {
- switchFloodlightAutoMode(false);
- }
- break;
- }
- super.handleCommand(channelUID, command);
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- switch (channelId) {
- case CHANNEL_CAMERA_FLOODLIGHT:
- return getFloodlightState();
- case CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE:
- // The auto-mode state shouldn't be updated, because this isn't a dedicated information. When the
- // floodlight is switched on the state within the Netatmo API is "on" and the information if the
- // previous
- // state was "auto" instead of "off" is lost... Therefore the binding handles its own auto-mode state.
- if (floodlightAutoModeState == UnDefType.UNDEF) {
- floodlightAutoModeState = getFloodlightAutoModeState();
- }
- return floodlightAutoModeState;
- }
- return super.getNAThingProperty(channelId);
- }
-
- private State getFloodlightState() {
- return getModule().map(m -> toOnOffType(m.getLightModeStatus() == NAWelcomeCamera.LightModeStatusEnum.ON))
- .orElse(UnDefType.UNDEF);
- }
-
- private State getFloodlightAutoModeState() {
- return getModule().map(m -> toOnOffType(m.getLightModeStatus() == NAWelcomeCamera.LightModeStatusEnum.AUTO))
- .orElse(UnDefType.UNDEF);
- }
-
- private void switchFloodlight(boolean isOn) {
- if (isOn) {
- changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.ON);
- } else {
- switchFloodlightAutoMode(floodlightAutoModeState == OnOffType.ON);
- }
- }
-
- private void switchFloodlightAutoMode(boolean isAutoMode) {
- floodlightAutoModeState = toOnOffType(isAutoMode);
- if (isAutoMode) {
- changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.AUTO);
- } else {
- changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum.OFF);
- }
- }
-
- private void changeFloodlightMode(NAWelcomeCamera.LightModeStatusEnum mode) {
- Optional<String> localCameraURL = getLocalCameraURL();
- if (localCameraURL.isPresent()) {
- String url = localCameraURL.get() + FLOODLIGHT_SET_URL_PATH + "?config=%7B%22mode%22:%22" + mode.toString()
- + "%22%7D";
- executeGETRequest(url);
-
- invalidateParentCacheAndRefresh();
- }
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.providers;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.core.thing.type.ChannelType;
+import org.openhab.core.thing.type.ChannelTypeBuilder;
+import org.openhab.core.thing.type.ChannelTypeProvider;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.thing.type.StateChannelTypeBuilder;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * Extends the ChannelTypeProvider generating Channel Types based on {@link MeasureClass} enum.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+@Component(service = { NetatmoChannelTypeProvider.class, ChannelTypeProvider.class })
+public class NetatmoChannelTypeProvider implements ChannelTypeProvider {
+ private final Collection<ChannelType> channelTypes = new HashSet<>();
+
+ public NetatmoChannelTypeProvider() {
+ MeasureClass.AS_SET.forEach(mc -> mc.channels.forEach((measureChannel, channelDetails) -> {
+ StateChannelTypeBuilder channelTypeBuilder = ChannelTypeBuilder
+ .state(new ChannelTypeUID(BINDING_ID, measureChannel), measureChannel.replace("-", " "),
+ channelDetails.itemType)
+ .withStateDescriptionFragment(channelDetails.stateDescriptionFragment)
+ .withConfigDescriptionURI(channelDetails.configURI);
+
+ channelTypes.add(channelTypeBuilder.build());
+ }));
+ }
+
+ @Override
+ public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
+ return Collections.unmodifiableCollection(channelTypes);
+ }
+
+ @Override
+ public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
+ return channelTypes.stream().filter(ct -> ct.getUID().equals(channelTypeUID)).findFirst().orElse(null);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.providers;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.events.EventPublisher;
+import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
+import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
+import org.openhab.core.thing.link.ItemChannelLinkRegistry;
+import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * Dynamic provider of state options while leaving other state description fields as original.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@Component(service = { DynamicStateDescriptionProvider.class, NetatmoDescriptionProvider.class })
+@NonNullByDefault
+public class NetatmoDescriptionProvider extends BaseDynamicStateDescriptionProvider {
+ @Activate
+ public NetatmoDescriptionProvider(final @Reference EventPublisher eventPublisher,
+ final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
+ final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
+ this.eventPublisher = eventPublisher;
+ this.itemChannelLinkRegistry = itemChannelLinkRegistry;
+ this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.providers;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.ModuleType;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.ThingTypeProvider;
+import org.openhab.core.thing.i18n.ThingTypeI18nLocalizationService;
+import org.openhab.core.thing.type.ChannelGroupDefinition;
+import org.openhab.core.thing.type.ChannelGroupTypeUID;
+import org.openhab.core.thing.type.ThingType;
+import org.openhab.core.thing.type.ThingTypeBuilder;
+import org.osgi.framework.Bundle;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extends the ThingTypeProvider to generated Thing types based on {@link ModuleType} enum.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+
+@Component(service = ThingTypeProvider.class)
+@NonNullByDefault
+public class NetatmoThingTypeProvider implements ThingTypeProvider {
+ private final Logger logger = LoggerFactory.getLogger(NetatmoThingTypeProvider.class);
+ private final ThingTypeI18nLocalizationService localizationService;
+ private final Bundle bundle;
+
+ @Activate
+ public NetatmoThingTypeProvider(final @Reference ThingTypeI18nLocalizationService localizationService,
+ ComponentContext componentContext) {
+ this.bundle = componentContext.getBundleContext().getBundle();
+ this.localizationService = localizationService;
+ }
+
+ @Override
+ public Collection<ThingType> getThingTypes(@Nullable Locale locale) {
+ return ModuleType.AS_SET.stream().filter(mt -> mt != ModuleType.UNKNOWN)
+ .map(mt -> Optional.ofNullable(getThingType(mt.thingTypeUID, locale))).map(Optional::get)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) {
+ if (BINDING_ID.equalsIgnoreCase(thingTypeUID.getBindingId())) {
+ try {
+ ModuleType moduleType = ModuleType.from(thingTypeUID);
+
+ ThingTypeBuilder thingTypeBuilder = ThingTypeBuilder.instance(thingTypeUID, thingTypeUID.toString())
+ .withRepresentationProperty(EQUIPMENT_ID).withExtensibleChannelTypeIds(moduleType.extensions)
+ .withChannelGroupDefinitions(getGroupDefinitions(moduleType))
+ .withConfigDescriptionURI(moduleType.getConfigDescription());
+
+ ThingTypeUID bridgeType = moduleType.getBridge().thingTypeUID;
+ if (!ModuleType.UNKNOWN.thingTypeUID.equals(bridgeType)) {
+ thingTypeBuilder.withSupportedBridgeTypeUIDs(List.of(bridgeType.getAsString()));
+ }
+
+ return localizationService.createLocalizedThingType(bundle,
+ moduleType.isABridge() ? thingTypeBuilder.buildBridge() : thingTypeBuilder.build(), locale);
+ } catch (IllegalArgumentException e) {
+ logger.warn("Unable to define ModuleType for thingType {} : {}", thingTypeUID.getId(), e.getMessage());
+ }
+ }
+ return null;
+ }
+
+ private List<ChannelGroupDefinition> getGroupDefinitions(ModuleType thingType) {
+ return thingType.groupTypes.stream().map(groupTypeName -> new ChannelGroupDefinition(toGroupName(groupTypeName),
+ new ChannelGroupTypeUID(BINDING_ID, groupTypeName))).collect(Collectors.toList());
+ }
+
+ public static String toGroupName(String groupeTypeName) {
+ return groupeTypeName.replace(OPTION_EXTENDED, "").replace(OPTION_OUTSIDE, "");
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.station;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.WeatherUtils;
-import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAMain;
-
-/**
- * {@link NAMainHandler} is the base class for all current Netatmo
- * weather station equipments (both modules and devices)
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class NAMainHandler extends NetatmoDeviceHandler<NAMain> {
- private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
-
- public NAMainHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- protected Optional<NAMain> updateReadings() {
- Optional<NAMain> result = getBridgeHandler().flatMap(handler -> handler.getStationsDataBody(getId()))
- .map(dataBody -> nonNullStream(dataBody.getDevices())
- .filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
- result.ifPresent(device -> {
- nonNullList(device.getModules()).forEach(child -> childs.put(child.getId(), child));
- });
-
- updateMeasurements();
-
- childs.keySet().forEach((childId) -> {
- findNAThing(childId).map(NetatmoModuleHandler.class::cast).ifPresent(naChildModule -> {
- naChildModule.updateMeasurements();
- });
- });
-
- return result;
- }
-
- @Override
- protected void updateProperties(NAMain deviceData) {
- updateProperties(deviceData.getFirmware(), deviceData.getType());
- }
-
- @Override
- public void updateMeasurements() {
- updateDayMeasurements();
- updateWeekMeasurements();
- updateMonthMeasurements();
- }
-
- private void updateDayMeasurements() {
- List<String> channels = new ArrayList<>();
- List<String> types = new ArrayList<>();
- addMeasurement(channels, types, CHANNEL_MIN_CO2, MIN_CO2);
- addMeasurement(channels, types, CHANNEL_MAX_CO2, MAX_CO2);
- addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY, MIN_HUM);
- addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY, MAX_HUM);
- addMeasurement(channels, types, CHANNEL_MIN_NOISE, MIN_NOISE);
- addMeasurement(channels, types, CHANNEL_MAX_NOISE, MAX_NOISE);
- addMeasurement(channels, types, CHANNEL_MIN_PRESSURE, MIN_PRESSURE);
- addMeasurement(channels, types, CHANNEL_MAX_PRESSURE, MAX_PRESSURE);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2, DATE_MIN_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2, DATE_MAX_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY, DATE_MIN_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY, DATE_MAX_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_NOISE, DATE_MIN_NOISE);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_NOISE, DATE_MAX_NOISE);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_PRESSURE, DATE_MIN_PRESSURE);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_PRESSURE, DATE_MAX_PRESSURE);
- if (!channels.isEmpty()) {
- getMeasurements(getId(), null, ONE_DAY, types, channels, channelMeasurements);
- }
- }
-
- private void updateWeekMeasurements() {
- List<String> channels = new ArrayList<>();
- List<String> types = new ArrayList<>();
- addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_WEEK, MIN_CO2);
- addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_WEEK, MAX_CO2);
- addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_WEEK, MIN_HUM);
- addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_WEEK, MAX_HUM);
- addMeasurement(channels, types, CHANNEL_MIN_NOISE_THIS_WEEK, MIN_NOISE);
- addMeasurement(channels, types, CHANNEL_MAX_NOISE_THIS_WEEK, MAX_NOISE);
- addMeasurement(channels, types, CHANNEL_MIN_PRESSURE_THIS_WEEK, MIN_PRESSURE);
- addMeasurement(channels, types, CHANNEL_MAX_PRESSURE_THIS_WEEK, MAX_PRESSURE);
- addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_WEEK, MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_WEEK, MAX_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_WEEK, DATE_MIN_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_WEEK, DATE_MAX_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK, DATE_MIN_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK, DATE_MAX_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_NOISE_THIS_WEEK, DATE_MIN_NOISE);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_NOISE_THIS_WEEK, DATE_MAX_NOISE);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_PRESSURE_THIS_WEEK, DATE_MIN_PRESSURE);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_PRESSURE_THIS_WEEK, DATE_MAX_PRESSURE);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_WEEK, DATE_MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_WEEK, DATE_MAX_TEMP);
- if (!channels.isEmpty()) {
- getMeasurements(getId(), null, ONE_WEEK, types, channels, channelMeasurements);
- }
- }
-
- private void updateMonthMeasurements() {
- List<String> channels = new ArrayList<>();
- List<String> types = new ArrayList<>();
- addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_MONTH, MIN_CO2);
- addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_MONTH, MAX_CO2);
- addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_MONTH, MIN_HUM);
- addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_MONTH, MAX_HUM);
- addMeasurement(channels, types, CHANNEL_MIN_NOISE_THIS_MONTH, MIN_NOISE);
- addMeasurement(channels, types, CHANNEL_MAX_NOISE_THIS_MONTH, MAX_NOISE);
- addMeasurement(channels, types, CHANNEL_MIN_PRESSURE_THIS_MONTH, MIN_PRESSURE);
- addMeasurement(channels, types, CHANNEL_MAX_PRESSURE_THIS_MONTH, MAX_PRESSURE);
- addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_MONTH, MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_MONTH, MAX_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_MONTH, DATE_MIN_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_MONTH, DATE_MAX_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH, DATE_MIN_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH, DATE_MAX_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_NOISE_THIS_MONTH, DATE_MIN_NOISE);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_NOISE_THIS_MONTH, DATE_MAX_NOISE);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_PRESSURE_THIS_MONTH, DATE_MIN_PRESSURE);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_PRESSURE_THIS_MONTH, DATE_MAX_PRESSURE);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_MONTH, DATE_MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_MONTH, DATE_MAX_TEMP);
- if (!channels.isEmpty()) {
- getMeasurements(getId(), null, ONE_MONTH, types, channels, channelMeasurements);
- }
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- NADashboardData dashboardData = getDevice().map(d -> d.getDashboardData()).orElse(null);
- if (dashboardData != null) {
- switch (channelId) {
- case CHANNEL_CO2:
- return toQuantityType(dashboardData.getCo2(), API_CO2_UNIT);
- case CHANNEL_TEMPERATURE:
- return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
- case CHANNEL_MIN_TEMP:
- return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
- case CHANNEL_MAX_TEMP:
- return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
- case CHANNEL_TEMP_TREND:
- return toStringType(dashboardData.getTempTrend());
- case CHANNEL_NOISE:
- return toQuantityType(dashboardData.getNoise(), API_NOISE_UNIT);
- case CHANNEL_PRESSURE:
- return toQuantityType(dashboardData.getPressure(), API_PRESSURE_UNIT);
- case CHANNEL_PRESS_TREND:
- return toStringType(dashboardData.getPressureTrend());
- case CHANNEL_ABSOLUTE_PRESSURE:
- return toQuantityType(dashboardData.getAbsolutePressure(), API_PRESSURE_UNIT);
- case CHANNEL_TIMEUTC:
- return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
- case CHANNEL_DATE_MIN_TEMP:
- return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
- case CHANNEL_DATE_MAX_TEMP:
- return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
- case CHANNEL_HUMIDITY:
- return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
- case CHANNEL_HUMIDEX:
- return toDecimalType(
- WeatherUtils.getHumidex(dashboardData.getTemperature(), dashboardData.getHumidity()));
- case CHANNEL_HEATINDEX:
- return toQuantityType(
- WeatherUtils.getHeatIndex(dashboardData.getTemperature(), dashboardData.getHumidity()),
- API_TEMPERATURE_UNIT);
- case CHANNEL_DEWPOINT:
- return toQuantityType(
- WeatherUtils.getDewPoint(dashboardData.getTemperature(), dashboardData.getHumidity()),
- API_TEMPERATURE_UNIT);
- case CHANNEL_DEWPOINTDEP:
- Double dewPoint = WeatherUtils.getDewPoint(dashboardData.getTemperature(),
- dashboardData.getHumidity());
- return toQuantityType(WeatherUtils.getDewPointDep(dashboardData.getTemperature(), dewPoint),
- API_TEMPERATURE_UNIT);
- }
- }
-
- switch (channelId) {
- case CHANNEL_MIN_CO2:
- case CHANNEL_MIN_CO2_THIS_WEEK:
- case CHANNEL_MIN_CO2_THIS_MONTH:
- case CHANNEL_MAX_CO2:
- case CHANNEL_MAX_CO2_THIS_WEEK:
- case CHANNEL_MAX_CO2_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_CO2_UNIT);
- case CHANNEL_MIN_HUMIDITY:
- case CHANNEL_MIN_HUMIDITY_THIS_WEEK:
- case CHANNEL_MIN_HUMIDITY_THIS_MONTH:
- case CHANNEL_MAX_HUMIDITY:
- case CHANNEL_MAX_HUMIDITY_THIS_WEEK:
- case CHANNEL_MAX_HUMIDITY_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_HUMIDITY_UNIT);
- case CHANNEL_MIN_NOISE:
- case CHANNEL_MIN_NOISE_THIS_WEEK:
- case CHANNEL_MIN_NOISE_THIS_MONTH:
- case CHANNEL_MAX_NOISE:
- case CHANNEL_MAX_NOISE_THIS_WEEK:
- case CHANNEL_MAX_NOISE_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_NOISE_UNIT);
- case CHANNEL_MIN_PRESSURE:
- case CHANNEL_MIN_PRESSURE_THIS_WEEK:
- case CHANNEL_MIN_PRESSURE_THIS_MONTH:
- case CHANNEL_MAX_PRESSURE:
- case CHANNEL_MAX_PRESSURE_THIS_WEEK:
- case CHANNEL_MAX_PRESSURE_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_PRESSURE_UNIT);
- case CHANNEL_MIN_TEMP_THIS_WEEK:
- case CHANNEL_MIN_TEMP_THIS_MONTH:
- case CHANNEL_MAX_TEMP_THIS_WEEK:
- case CHANNEL_MAX_TEMP_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_TEMPERATURE_UNIT);
- case CHANNEL_DATE_MIN_CO2:
- case CHANNEL_DATE_MIN_CO2_THIS_WEEK:
- case CHANNEL_DATE_MIN_CO2_THIS_MONTH:
- case CHANNEL_DATE_MAX_CO2:
- case CHANNEL_DATE_MAX_CO2_THIS_WEEK:
- case CHANNEL_DATE_MAX_CO2_THIS_MONTH:
- case CHANNEL_DATE_MIN_NOISE:
- case CHANNEL_DATE_MIN_NOISE_THIS_WEEK:
- case CHANNEL_DATE_MIN_NOISE_THIS_MONTH:
- case CHANNEL_DATE_MAX_NOISE:
- case CHANNEL_DATE_MAX_NOISE_THIS_WEEK:
- case CHANNEL_DATE_MAX_NOISE_THIS_MONTH:
- case CHANNEL_DATE_MIN_HUMIDITY:
- case CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK:
- case CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH:
- case CHANNEL_DATE_MAX_HUMIDITY:
- case CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK:
- case CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH:
- case CHANNEL_DATE_MIN_PRESSURE:
- case CHANNEL_DATE_MIN_PRESSURE_THIS_WEEK:
- case CHANNEL_DATE_MIN_PRESSURE_THIS_MONTH:
- case CHANNEL_DATE_MAX_PRESSURE:
- case CHANNEL_DATE_MAX_PRESSURE_THIS_WEEK:
- case CHANNEL_DATE_MAX_PRESSURE_THIS_MONTH:
- case CHANNEL_DATE_MIN_TEMP_THIS_WEEK:
- case CHANNEL_DATE_MIN_TEMP_THIS_MONTH:
- case CHANNEL_DATE_MAX_TEMP_THIS_WEEK:
- case CHANNEL_DATE_MAX_TEMP_THIS_MONTH:
- return toDateTimeType(channelMeasurements.get(channelId), timeZoneProvider.getTimeZone());
- }
-
- return super.getNAThingProperty(channelId);
- }
-
- @Override
- protected Optional<Integer> getDataTimestamp() {
- return getDevice().map(d -> d.getLastStatusStore());
- }
-
- @Override
- protected boolean isReachable() {
- boolean result = false;
- Optional<NAMain> device = getDevice();
- if (device.isPresent()) {
- Boolean reachable = device.get().isReachable();
- result = reachable != null ? reachable.booleanValue() : false;
- }
- return result;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.station;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.WeatherUtils;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAStationModule;
-
-/**
- * {@link NAModule1Handler} is the class used to handle the outdoor module
- * capable of reporting temperature and humidity
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class NAModule1Handler extends NetatmoModuleHandler<NAStationModule> {
- private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
-
- public NAModule1Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- protected void updateProperties(NAStationModule moduleData) {
- updateProperties(moduleData.getFirmware(), moduleData.getType());
- }
-
- @Override
- public void updateMeasurements() {
- updateDayMeasurements();
- updateWeekMeasurements();
- updateMonthMeasurements();
- }
-
- private void updateDayMeasurements() {
- List<String> channels = new ArrayList<>();
- List<String> types = new ArrayList<>();
- addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY, MIN_HUM);
- addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY, MAX_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY, DATE_MIN_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY, DATE_MAX_HUM);
- if (!channels.isEmpty()) {
- getMeasurements(getParentId(), getId(), ONE_DAY, types, channels, channelMeasurements);
- }
- }
-
- private void updateWeekMeasurements() {
- List<String> channels = new ArrayList<>();
- List<String> types = new ArrayList<>();
- addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_WEEK, MIN_HUM);
- addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_WEEK, MAX_HUM);
- addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_WEEK, MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_WEEK, MAX_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK, DATE_MIN_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK, DATE_MAX_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_WEEK, DATE_MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_WEEK, DATE_MAX_TEMP);
- if (!channels.isEmpty()) {
- getMeasurements(getParentId(), getId(), ONE_WEEK, types, channels, channelMeasurements);
- }
- }
-
- private void updateMonthMeasurements() {
- List<String> channels = new ArrayList<>();
- List<String> types = new ArrayList<>();
- addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_MONTH, MIN_HUM);
- addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_MONTH, MAX_HUM);
- addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_MONTH, MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_MONTH, MAX_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH, DATE_MIN_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH, DATE_MAX_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_MONTH, DATE_MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_MONTH, DATE_MAX_TEMP);
- if (!channels.isEmpty()) {
- getMeasurements(getParentId(), getId(), ONE_MONTH, types, channels, channelMeasurements);
- }
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
- if (dashboardData != null) {
- switch (channelId) {
- case CHANNEL_TEMP_TREND:
- return toStringType(dashboardData.getTempTrend());
- case CHANNEL_TEMPERATURE:
- return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
- case CHANNEL_DATE_MIN_TEMP:
- return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
- case CHANNEL_DATE_MAX_TEMP:
- return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
- case CHANNEL_MIN_TEMP:
- return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
- case CHANNEL_MAX_TEMP:
- return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
- case CHANNEL_HUMIDITY:
- return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
- case CHANNEL_TIMEUTC:
- return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
- case CHANNEL_HUMIDEX:
- return toDecimalType(
- WeatherUtils.getHumidex(dashboardData.getTemperature(), dashboardData.getHumidity()));
- case CHANNEL_HEATINDEX:
- return toQuantityType(
- WeatherUtils.getHeatIndex(dashboardData.getTemperature(), dashboardData.getHumidity()),
- API_TEMPERATURE_UNIT);
- case CHANNEL_DEWPOINT:
- return toQuantityType(
- WeatherUtils.getDewPoint(dashboardData.getTemperature(), dashboardData.getHumidity()),
- API_TEMPERATURE_UNIT);
- case CHANNEL_DEWPOINTDEP:
- Double dewpoint = WeatherUtils.getDewPoint(dashboardData.getTemperature(),
- dashboardData.getHumidity());
- return toQuantityType(WeatherUtils.getDewPointDep(dashboardData.getTemperature(), dewpoint),
- API_TEMPERATURE_UNIT);
- }
- }
-
- switch (channelId) {
- case CHANNEL_MIN_HUMIDITY:
- case CHANNEL_MIN_HUMIDITY_THIS_WEEK:
- case CHANNEL_MIN_HUMIDITY_THIS_MONTH:
- case CHANNEL_MAX_HUMIDITY:
- case CHANNEL_MAX_HUMIDITY_THIS_WEEK:
- case CHANNEL_MAX_HUMIDITY_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_HUMIDITY_UNIT);
- case CHANNEL_MIN_TEMP_THIS_WEEK:
- case CHANNEL_MIN_TEMP_THIS_MONTH:
- case CHANNEL_MAX_TEMP_THIS_WEEK:
- case CHANNEL_MAX_TEMP_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_TEMPERATURE_UNIT);
- case CHANNEL_DATE_MIN_HUMIDITY:
- case CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK:
- case CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH:
- case CHANNEL_DATE_MAX_HUMIDITY:
- case CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK:
- case CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH:
- case CHANNEL_DATE_MIN_TEMP_THIS_WEEK:
- case CHANNEL_DATE_MIN_TEMP_THIS_MONTH:
- case CHANNEL_DATE_MAX_TEMP_THIS_WEEK:
- case CHANNEL_DATE_MAX_TEMP_THIS_MONTH:
- return toDateTimeType(channelMeasurements.get(channelId), timeZoneProvider.getTimeZone());
- }
-
- return super.getNAThingProperty(channelId);
- }
-
- @Override
- protected boolean isReachable() {
- boolean result = false;
- Optional<NAStationModule> module = getModule();
- if (module.isPresent()) {
- Boolean reachable = module.get().isReachable();
- result = reachable != null ? reachable.booleanValue() : false;
- }
- return result;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.station;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAStationModule;
-
-/**
- * {@link NAModule2Handler} is the class used to handle the wind module
- * capable of reporting wind angle and strength
- *
- * @author Gaël L'hopital - Initial contribution
- */
-@NonNullByDefault
-public class NAModule2Handler extends NetatmoModuleHandler<NAStationModule> {
-
- public NAModule2Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- protected void updateProperties(NAStationModule moduleData) {
- updateProperties(moduleData.getFirmware(), moduleData.getType());
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
- if (dashboardData != null) {
- switch (channelId) {
- case CHANNEL_WIND_ANGLE:
- return toQuantityType(dashboardData.getWindAngle(), API_WIND_DIRECTION_UNIT);
- case CHANNEL_WIND_STRENGTH:
- return toQuantityType(dashboardData.getWindStrength(), API_WIND_SPEED_UNIT);
- case CHANNEL_GUST_ANGLE:
- return toQuantityType(dashboardData.getGustAngle(), API_WIND_DIRECTION_UNIT);
- case CHANNEL_GUST_STRENGTH:
- return toQuantityType(dashboardData.getGustStrength(), API_WIND_SPEED_UNIT);
- case CHANNEL_TIMEUTC:
- return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
- case CHANNEL_MAX_WIND_STRENGTH:
- return toQuantityType(dashboardData.getMaxWindStr(), API_WIND_SPEED_UNIT);
- case CHANNEL_DATE_MAX_WIND_STRENGTH:
- return toDateTimeType(dashboardData.getDateMaxWindStr(), timeZoneProvider.getTimeZone());
- }
- }
- return super.getNAThingProperty(channelId);
- }
-
- @Override
- protected boolean isReachable() {
- boolean result = false;
- Optional<NAStationModule> module = getModule();
- if (module.isPresent()) {
- Boolean reachable = module.get().isReachable();
- result = reachable != null ? reachable.booleanValue() : false;
- }
- return result;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.station;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAStationModule;
-
-/**
- * {@link NAModule3Handler} is the class used to handle the Rain Gauge
- * capable of measuring precipitation
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class NAModule3Handler extends NetatmoModuleHandler<NAStationModule> {
- private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
-
- public NAModule3Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- protected void updateProperties(NAStationModule moduleData) {
- updateProperties(moduleData.getFirmware(), moduleData.getType());
- }
-
- @Override
- public void updateMeasurements() {
- List<String> types = Arrays.asList(SUM_RAIN);
-
- if (isLinked(CHANNEL_SUM_RAIN_THIS_WEEK)) {
- getMeasurements(getParentId(), getId(), ONE_WEEK, types, Arrays.asList(CHANNEL_SUM_RAIN_THIS_WEEK),
- channelMeasurements);
- }
-
- if (isLinked(CHANNEL_SUM_RAIN_THIS_MONTH)) {
- getMeasurements(getParentId(), getId(), ONE_MONTH, types, Arrays.asList(CHANNEL_SUM_RAIN_THIS_MONTH),
- channelMeasurements);
- }
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
- if (dashboardData != null) {
- switch (channelId) {
- case CHANNEL_RAIN:
- return toQuantityType(dashboardData.getRain(), API_RAIN_UNIT);
- case CHANNEL_SUM_RAIN1:
- return toQuantityType(dashboardData.getSumRain1(), API_RAIN_UNIT);
- case CHANNEL_SUM_RAIN24:
- return toQuantityType(dashboardData.getSumRain24(), API_RAIN_UNIT);
- case CHANNEL_TIMEUTC:
- return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
- }
- }
-
- switch (channelId) {
- case CHANNEL_SUM_RAIN_THIS_WEEK:
- case CHANNEL_SUM_RAIN_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_RAIN_UNIT);
- }
-
- return super.getNAThingProperty(channelId);
- }
-
- @Override
- protected boolean isReachable() {
- boolean result = false;
- Optional<NAStationModule> module = getModule();
- if (module.isPresent()) {
- Boolean reachable = module.get().isReachable();
- result = reachable != null ? reachable.booleanValue() : false;
- }
- return result;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.station;
-
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.WeatherUtils;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-
-import io.swagger.client.model.NADashboardData;
-import io.swagger.client.model.NAStationModule;
-
-/**
- * {@link NAModule4Handler} is the class used to handle the additional
- * indoor module capable of reporting temperature, humidity and CO2 level
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
- *
- */
-@NonNullByDefault
-public class NAModule4Handler extends NetatmoModuleHandler<NAStationModule> {
- private Map<String, Float> channelMeasurements = new ConcurrentHashMap<>();
-
- public NAModule4Handler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- protected void updateProperties(NAStationModule moduleData) {
- updateProperties(moduleData.getFirmware(), moduleData.getType());
- }
-
- @Override
- public void updateMeasurements() {
- updateDayMeasurements();
- updateWeekMeasurements();
- updateMonthMeasurements();
- }
-
- private void updateDayMeasurements() {
- List<String> channels = new ArrayList<>();
- List<String> types = new ArrayList<>();
- addMeasurement(channels, types, CHANNEL_MIN_CO2, MIN_CO2);
- addMeasurement(channels, types, CHANNEL_MAX_CO2, MAX_CO2);
- addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY, MIN_HUM);
- addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY, MAX_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2, DATE_MIN_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2, DATE_MAX_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY, DATE_MIN_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY, DATE_MAX_HUM);
- if (!channels.isEmpty()) {
- getMeasurements(getParentId(), getId(), ONE_DAY, types, channels, channelMeasurements);
- }
- }
-
- private void updateWeekMeasurements() {
- List<String> channels = new ArrayList<>();
- List<String> types = new ArrayList<>();
- addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_WEEK, MIN_CO2);
- addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_WEEK, MAX_CO2);
- addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_WEEK, MIN_HUM);
- addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_WEEK, MAX_HUM);
- addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_WEEK, MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_WEEK, MAX_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_WEEK, DATE_MIN_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_WEEK, DATE_MAX_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK, DATE_MIN_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK, DATE_MAX_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_WEEK, DATE_MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_WEEK, DATE_MAX_TEMP);
- if (!channels.isEmpty()) {
- getMeasurements(getParentId(), getId(), ONE_WEEK, types, channels, channelMeasurements);
- }
- }
-
- private void updateMonthMeasurements() {
- List<String> channels = new ArrayList<>();
- List<String> types = new ArrayList<>();
- addMeasurement(channels, types, CHANNEL_MIN_CO2_THIS_MONTH, MIN_CO2);
- addMeasurement(channels, types, CHANNEL_MAX_CO2_THIS_MONTH, MAX_CO2);
- addMeasurement(channels, types, CHANNEL_MIN_HUMIDITY_THIS_MONTH, MIN_HUM);
- addMeasurement(channels, types, CHANNEL_MAX_HUMIDITY_THIS_MONTH, MAX_HUM);
- addMeasurement(channels, types, CHANNEL_MIN_TEMP_THIS_MONTH, MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_MAX_TEMP_THIS_MONTH, MAX_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_CO2_THIS_MONTH, DATE_MIN_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_CO2_THIS_MONTH, DATE_MAX_CO2);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH, DATE_MIN_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH, DATE_MAX_HUM);
- addMeasurement(channels, types, CHANNEL_DATE_MIN_TEMP_THIS_MONTH, DATE_MIN_TEMP);
- addMeasurement(channels, types, CHANNEL_DATE_MAX_TEMP_THIS_MONTH, DATE_MAX_TEMP);
- if (!channels.isEmpty()) {
- getMeasurements(getParentId(), getId(), ONE_MONTH, types, channels, channelMeasurements);
- }
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- NADashboardData dashboardData = getModule().map(m -> m.getDashboardData()).orElse(null);
- if (dashboardData != null) {
- switch (channelId) {
- case CHANNEL_TEMP_TREND:
- return toStringType(dashboardData.getTempTrend());
- case CHANNEL_CO2:
- return toQuantityType(dashboardData.getCo2(), API_CO2_UNIT);
- case CHANNEL_TEMPERATURE:
- return toQuantityType(dashboardData.getTemperature(), API_TEMPERATURE_UNIT);
- case CHANNEL_DATE_MIN_TEMP:
- return toDateTimeType(dashboardData.getDateMinTemp(), timeZoneProvider.getTimeZone());
- case CHANNEL_DATE_MAX_TEMP:
- return toDateTimeType(dashboardData.getDateMaxTemp(), timeZoneProvider.getTimeZone());
- case CHANNEL_MIN_TEMP:
- return toQuantityType(dashboardData.getMinTemp(), API_TEMPERATURE_UNIT);
- case CHANNEL_MAX_TEMP:
- return toQuantityType(dashboardData.getMaxTemp(), API_TEMPERATURE_UNIT);
- case CHANNEL_TIMEUTC:
- return toDateTimeType(dashboardData.getTimeUtc(), timeZoneProvider.getTimeZone());
- case CHANNEL_HUMIDITY:
- return toQuantityType(dashboardData.getHumidity(), API_HUMIDITY_UNIT);
- case CHANNEL_HUMIDEX:
- return toDecimalType(
- WeatherUtils.getHumidex(dashboardData.getTemperature(), dashboardData.getHumidity()));
- case CHANNEL_HEATINDEX:
- return toQuantityType(
- WeatherUtils.getHeatIndex(dashboardData.getTemperature(), dashboardData.getHumidity()),
- API_TEMPERATURE_UNIT);
- case CHANNEL_DEWPOINT:
- return toQuantityType(
- WeatherUtils.getDewPoint(dashboardData.getTemperature(), dashboardData.getHumidity()),
- API_TEMPERATURE_UNIT);
- case CHANNEL_DEWPOINTDEP:
- Double dewpoint = WeatherUtils.getDewPoint(dashboardData.getTemperature(),
- dashboardData.getHumidity());
- return toQuantityType(WeatherUtils.getDewPointDep(dashboardData.getTemperature(), dewpoint),
- API_TEMPERATURE_UNIT);
- }
- }
-
- switch (channelId) {
- case CHANNEL_MIN_CO2:
- case CHANNEL_MIN_CO2_THIS_WEEK:
- case CHANNEL_MIN_CO2_THIS_MONTH:
- case CHANNEL_MAX_CO2:
- case CHANNEL_MAX_CO2_THIS_WEEK:
- case CHANNEL_MAX_CO2_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_CO2_UNIT);
- case CHANNEL_MIN_HUMIDITY:
- case CHANNEL_MIN_HUMIDITY_THIS_WEEK:
- case CHANNEL_MIN_HUMIDITY_THIS_MONTH:
- case CHANNEL_MAX_HUMIDITY:
- case CHANNEL_MAX_HUMIDITY_THIS_WEEK:
- case CHANNEL_MAX_HUMIDITY_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_HUMIDITY_UNIT);
- case CHANNEL_MIN_TEMP_THIS_WEEK:
- case CHANNEL_MIN_TEMP_THIS_MONTH:
- case CHANNEL_MAX_TEMP_THIS_WEEK:
- case CHANNEL_MAX_TEMP_THIS_MONTH:
- return toQuantityType(channelMeasurements.get(channelId), API_TEMPERATURE_UNIT);
- case CHANNEL_DATE_MIN_CO2:
- case CHANNEL_DATE_MIN_CO2_THIS_WEEK:
- case CHANNEL_DATE_MIN_CO2_THIS_MONTH:
- case CHANNEL_DATE_MAX_CO2:
- case CHANNEL_DATE_MAX_CO2_THIS_WEEK:
- case CHANNEL_DATE_MAX_CO2_THIS_MONTH:
- case CHANNEL_DATE_MIN_HUMIDITY:
- case CHANNEL_DATE_MIN_HUMIDITY_THIS_WEEK:
- case CHANNEL_DATE_MIN_HUMIDITY_THIS_MONTH:
- case CHANNEL_DATE_MAX_HUMIDITY:
- case CHANNEL_DATE_MAX_HUMIDITY_THIS_WEEK:
- case CHANNEL_DATE_MAX_HUMIDITY_THIS_MONTH:
- case CHANNEL_DATE_MIN_TEMP_THIS_WEEK:
- case CHANNEL_DATE_MIN_TEMP_THIS_MONTH:
- case CHANNEL_DATE_MAX_TEMP_THIS_WEEK:
- case CHANNEL_DATE_MAX_TEMP_THIS_MONTH:
- return toDateTimeType(channelMeasurements.get(channelId), timeZoneProvider.getTimeZone());
- }
-
- return super.getNAThingProperty(channelId);
- }
-
- @Override
- protected boolean isReachable() {
- boolean result = false;
- Optional<NAStationModule> module = getModule();
- if (module.isPresent()) {
- Boolean reachable = module.get().isReachable();
- result = reachable != null ? reachable.booleanValue() : false;
- }
- return result;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.thermostat;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.time.ZonedDateTime;
-import java.time.temporal.TemporalAdjusters;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-import io.swagger.client.model.NAPlug;
-import io.swagger.client.model.NAYearMonth;
-
-/**
- * {@link NAPlugHandler} is the class used to handle the plug
- * device of a thermostat set
- *
- * @author Gaël L'hopital - Initial contribution OH2 version
- *
- */
-@NonNullByDefault
-public class NAPlugHandler extends NetatmoDeviceHandler<NAPlug> {
-
- public NAPlugHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- protected Optional<NAPlug> updateReadings() {
- Optional<NAPlug> result = getBridgeHandler().flatMap(handler -> handler.getThermostatsDataBody(getId()))
- .map(dataBody -> nonNullStream(dataBody.getDevices())
- .filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
- result.ifPresent(device -> {
- nonNullList(device.getModules()).forEach(child -> childs.put(child.getId(), child));
- });
- return result;
- }
-
- @Override
- protected void updateProperties(NAPlug deviceData) {
- updateProperties(deviceData.getFirmware(), deviceData.getType());
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- switch (channelId) {
- case CHANNEL_CONNECTED_BOILER:
- return getDevice().map(d -> toOnOffType(d.getPlugConnectedBoiler())).orElse(UnDefType.UNDEF);
- case CHANNEL_LAST_PLUG_SEEN:
- return getDevice().map(d -> toDateTimeType(d.getLastPlugSeen(), timeZoneProvider.getTimeZone()))
- .orElse(UnDefType.UNDEF);
- case CHANNEL_LAST_BILAN:
- return toDateTimeType(getLastBilan());
- }
- return super.getNAThingProperty(channelId);
- }
-
- public @Nullable ZonedDateTime getLastBilan() {
- Optional<NAYearMonth> lastBilan = getDevice().map(d -> d.getLastBilan());
- if (lastBilan.isPresent()) {
- ZonedDateTime zonedDT = ZonedDateTime.of(lastBilan.get().getY(), lastBilan.get().getM(), 1, 0, 0, 0, 0,
- ZonedDateTime.now().getZone());
- return zonedDT.with(TemporalAdjusters.lastDayOfMonth());
- }
- return null;
- }
-
- @Override
- protected Optional<Integer> getDataTimestamp() {
- return getDevice().map(d -> d.getLastStatusStore());
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.thermostat;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-import javax.measure.quantity.Temperature;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.NATherm1StateDescriptionProvider;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.StateOption;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.swagger.client.api.ThermostatApi;
-import io.swagger.client.model.NASetpoint;
-import io.swagger.client.model.NAThermProgram;
-import io.swagger.client.model.NAThermostat;
-import io.swagger.client.model.NATimeTableItem;
-import io.swagger.client.model.NAZone;
-
-/**
- * {@link NATherm1Handler} is the class used to handle the thermostat
- * module of a thermostat set
- *
- * @author Gaël L'hopital - Initial contribution OH2 version
- *
- */
-@NonNullByDefault
-public class NATherm1Handler extends NetatmoModuleHandler<NAThermostat> {
- private final Logger logger = LoggerFactory.getLogger(NATherm1Handler.class);
- private final NATherm1StateDescriptionProvider stateDescriptionProvider;
-
- public NATherm1Handler(Thing thing, NATherm1StateDescriptionProvider stateDescriptionProvider,
- final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- this.stateDescriptionProvider = stateDescriptionProvider;
- }
-
- @Override
- protected void updateProperties(NAThermostat moduleData) {
- updateProperties(moduleData.getFirmware(), moduleData.getType());
- }
-
- @Override
- public void updateChannels(Object moduleObject) {
- super.updateChannels(moduleObject);
- getModule().ifPresent(this::updateStateDescription);
- }
-
- private void updateStateDescription(NAThermostat thermostat) {
- List<StateOption> options = new ArrayList<>();
- for (NAThermProgram planning : nonNullList(thermostat.getThermProgramList())) {
- options.add(new StateOption(planning.getProgramId(), planning.getName()));
- }
- stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_PLANNING), options);
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- Optional<NAThermostat> thermostat = getModule();
- switch (channelId) {
- case CHANNEL_THERM_ORIENTATION:
- return thermostat.map(m -> toDecimalType(m.getThermOrientation())).orElse(UnDefType.UNDEF);
- case CHANNEL_THERM_RELAY:
- return thermostat.map(m -> m.getThermRelayCmd() == 100 ? (State) OnOffType.ON : (State) OnOffType.OFF)
- .orElse(UnDefType.UNDEF);
- case CHANNEL_TEMPERATURE:
- return thermostat.map(m -> toQuantityType(m.getMeasured().getTemperature(), API_TEMPERATURE_UNIT))
- .orElse(UnDefType.UNDEF);
- case CHANNEL_SETPOINT_TEMP:
- return getCurrentSetpoint();
- case CHANNEL_TIMEUTC:
- return thermostat.map(m -> toDateTimeType(m.getMeasured().getTime(), timeZoneProvider.getTimeZone()))
- .orElse(UnDefType.UNDEF);
- case CHANNEL_SETPOINT_END_TIME: {
- if (thermostat.isPresent()) {
- NASetpoint setpoint = thermostat.get().getSetpoint();
- if (setpoint != null) {
- Integer endTime = setpoint.getSetpointEndtime();
- if (endTime == null) {
- endTime = getNextProgramTime(nonNullList(thermostat.get().getThermProgramList()));
- }
- return toDateTimeType(endTime, timeZoneProvider.getTimeZone());
- }
- return UnDefType.NULL;
- }
- return UnDefType.UNDEF;
- }
- case CHANNEL_SETPOINT_MODE:
- return getSetpoint();
- case CHANNEL_PLANNING: {
- String currentPlanning = "-";
- if (thermostat.isPresent()) {
- for (NAThermProgram program : nonNullList(thermostat.get().getThermProgramList())) {
- if (Boolean.TRUE.equals(program.isSelected())) {
- currentPlanning = program.getProgramId();
- }
- }
- return toStringType(currentPlanning);
- }
- return UnDefType.UNDEF;
- }
- }
- return super.getNAThingProperty(channelId);
- }
-
- private State getSetpoint() {
- return getModule()
- .map(m -> m.getSetpoint() != null ? toStringType(m.getSetpoint().getSetpointMode()) : UnDefType.NULL)
- .orElse(UnDefType.UNDEF);
- }
-
- private State getCurrentSetpoint() {
- Optional<NAThermostat> thermostat = getModule();
- if (thermostat.isPresent()) {
- NASetpoint setPoint = thermostat.get().getSetpoint();
- if (setPoint != null) {
- String currentMode = setPoint.getSetpointMode();
-
- NAThermProgram currentProgram = nonNullStream(thermostat.get().getThermProgramList())
- .filter(p -> p.isSelected() != null && p.isSelected()).findFirst().get();
- switch (currentMode) {
- case CHANNEL_SETPOINT_MODE_MANUAL:
- return toDecimalType(setPoint.getSetpointTemp());
- case CHANNEL_SETPOINT_MODE_AWAY:
- NAZone zone = getZone(currentProgram.getZones(), 2);
- return toDecimalType(zone.getTemp());
- case CHANNEL_SETPOINT_MODE_HG:
- NAZone zone1 = getZone(currentProgram.getZones(), 3);
- return toDecimalType(zone1.getTemp());
- case CHANNEL_SETPOINT_MODE_PROGRAM:
- NATimeTableItem currentProgramMode = getCurrentProgramMode(
- nonNullList(thermostat.get().getThermProgramList()));
- if (currentProgramMode != null) {
- NAZone zone2 = getZone(currentProgram.getZones(), currentProgramMode.getId());
- return toDecimalType(zone2.getTemp());
- }
- case CHANNEL_SETPOINT_MODE_OFF:
- case CHANNEL_SETPOINT_MODE_MAX:
- return UnDefType.UNDEF;
- }
- }
- }
- return UnDefType.NULL;
- }
-
- private NAZone getZone(List<NAZone> zones, int searchedId) {
- return nonNullStream(zones).filter(z -> z.getId() == searchedId).findFirst().get();
- }
-
- private long getNetatmoProgramBaseTime() {
- Calendar mondayZero = Calendar.getInstance();
- mondayZero.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
- mondayZero.set(Calendar.HOUR_OF_DAY, 0);
- mondayZero.set(Calendar.MINUTE, 0);
- mondayZero.set(Calendar.SECOND, 0);
- return mondayZero.getTimeInMillis();
- }
-
- private @Nullable NATimeTableItem getCurrentProgramMode(List<NAThermProgram> thermProgramList) {
- NATimeTableItem lastProgram = null;
- Calendar now = Calendar.getInstance();
- long diff = (now.getTimeInMillis() - getNetatmoProgramBaseTime()) / 1000 / 60;
-
- Optional<NAThermProgram> currentProgram = thermProgramList.stream()
- .filter(p -> p.isSelected() != null && p.isSelected()).findFirst();
-
- if (currentProgram.isPresent()) {
- Stream<NATimeTableItem> pastPrograms = nonNullStream(currentProgram.get().getTimetable())
- .filter(t -> t.getMOffset() < diff);
- Optional<NATimeTableItem> program = pastPrograms.reduce((first, second) -> second);
- if (program.isPresent()) {
- lastProgram = program.get();
- }
- }
-
- return lastProgram;
- }
-
- private int getNextProgramTime(List<NAThermProgram> thermProgramList) {
- Calendar now = Calendar.getInstance();
- long diff = (now.getTimeInMillis() - getNetatmoProgramBaseTime()) / 1000 / 60;
-
- int result = -1;
-
- for (NAThermProgram thermProgram : thermProgramList) {
- if (thermProgram.isSelected() != null && thermProgram.isSelected()) {
- // By default we'll use the first slot of next week - this case will be true if
- // we are in the last schedule of the week so below loop will not exit by break
- List<NATimeTableItem> timetable = thermProgram.getTimetable();
- int next = timetable.get(0).getMOffset() + (7 * 24 * 60);
-
- for (NATimeTableItem timeTable : timetable) {
- if (timeTable.getMOffset() > diff) {
- next = timeTable.getMOffset();
- break;
- }
- }
-
- result = (int) (next * 60 + (getNetatmoProgramBaseTime() / 1000));
- }
- }
- return result;
- }
-
- @Override
- public void handleCommand(ChannelUID channelUID, Command command) {
- super.handleCommand(channelUID, command);
- if (!(command instanceof RefreshType)) {
- try {
- switch (channelUID.getId()) {
- case CHANNEL_SETPOINT_MODE: {
- String targetMode = command.toString();
- if (CHANNEL_SETPOINT_MODE_MANUAL.equals(targetMode)) {
- logger.info(
- "Switching to manual mode is done by assigning a setpoint temperature - command dropped");
- updateState(channelUID, getSetpoint());
- } else {
- pushSetpointUpdate(targetMode, null, null);
- }
- break;
- }
- case CHANNEL_SETPOINT_TEMP: {
- BigDecimal spTemp = null;
- if (command instanceof QuantityType) {
- @SuppressWarnings("unchecked")
- QuantityType<Temperature> quantity = ((QuantityType<Temperature>) command)
- .toUnit(API_TEMPERATURE_UNIT);
- if (quantity != null) {
- spTemp = quantity.toBigDecimal().setScale(1, RoundingMode.HALF_UP);
- }
- } else {
- spTemp = new BigDecimal(command.toString()).setScale(1, RoundingMode.HALF_UP);
- }
- if (spTemp != null) {
- pushSetpointUpdate(CHANNEL_SETPOINT_MODE_MANUAL, getSetpointEndTime(), spTemp.floatValue());
- }
-
- break;
- }
- case CHANNEL_PLANNING: {
- getApi().ifPresent(api -> {
- api.switchschedule(getParentId(), getId(), command.toString());
- updateState(channelUID, new StringType(command.toString()));
- invalidateParentCacheAndRefresh();
- });
- }
- }
- } catch (Exception e) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
- }
- }
- }
-
- private void pushSetpointUpdate(String target_mode, @Nullable Integer setpointEndtime,
- @Nullable Float setpointTemp) {
- getApi().ifPresent(api -> {
- api.setthermpoint(getParentId(), getId(), target_mode, setpointEndtime, setpointTemp);
- invalidateParentCacheAndRefresh();
- });
- }
-
- private int getSetpointEndTime() {
- Calendar cal = Calendar.getInstance();
- cal.add(Calendar.MINUTE, getSetPointDefaultDuration());
- cal.set(Calendar.SECOND, 0);
- cal.set(Calendar.MILLISECOND, 0);
- return (int) (cal.getTimeInMillis() / 1000);
- }
-
- private int getSetPointDefaultDuration() {
- // TODO : this informations could be sourced from Netatmo API instead of a local configuration element
- Configuration conf = config;
- Object defaultDuration = conf != null ? conf.get(SETPOINT_DEFAULT_DURATION) : null;
- if (defaultDuration instanceof BigDecimal) {
- return ((BigDecimal) defaultDuration).intValue();
- }
- return 60;
- }
-
- private Optional<ThermostatApi> getApi() {
- return getBridgeHandler().flatMap(handler -> handler.getThermostatApi());
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Measure;
+import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass;
+import org.openhab.core.io.net.http.HttpUtil;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.RawType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * This class holds various channel values conversion methods
+ *
+ * @author Gaël L'hopital - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ChannelTypeUtils {
+
+ public static @Nullable QuantityType<?> commandToQuantity(Command command, MeasureClass measureClass) {
+ Measure measureDef = measureClass.measureDefinition;
+ if (command instanceof QuantityType<?>) {
+ return ((QuantityType<?>) command).toUnit(measureDef.unit);
+ }
+ try {
+ double value = Double.parseDouble(command.toString());
+ return QuantityType.valueOf(value, measureDef.unit);
+ } catch (NumberFormatException ignore) {
+ }
+ return null;
+ }
+
+ public static State toStringType(String pattern, Object... parms) {
+ return toStringType(String.format(pattern, parms));
+ }
+
+ public static State toStringType(@Nullable Enum<?> value) {
+ return (value == null) ? UnDefType.NULL : new StringType(value.name());
+ }
+
+ public static State toStringType(@Nullable String value) {
+ return (value == null) ? UnDefType.NULL : new StringType(value);
+ }
+
+ public static State toDateTimeType(@Nullable ZonedDateTime zonedDateTime) {
+ return (zonedDateTime == null) ? UnDefType.NULL : new DateTimeType(zonedDateTime);
+ }
+
+ public static State toDateTimeType(Optional<ZonedDateTime> zonedDateTime) {
+ return zonedDateTime.map(zdt -> (State) new DateTimeType(zdt)).orElse(UnDefType.NULL);
+ }
+
+ public static State toQuantityType(@Nullable Double value, @Nullable MeasureClass measureClass) {
+ if (value != null && !value.isNaN()) {
+ if (measureClass != null) {
+ Measure measureDef = measureClass.measureDefinition;
+ BigDecimal measure = new BigDecimal(Math.min(measureDef.maxValue, Math.max(measureDef.minValue, value)))
+ .setScale(measureDef.scale, RoundingMode.HALF_UP);
+ return new QuantityType<>(measure, measureDef.unit);
+ } else {
+ return new DecimalType(value);
+ }
+ }
+ return UnDefType.NULL;
+ }
+
+ public static State toQuantityType(@Nullable Number value, Unit<?> unit) {
+ return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
+ }
+
+ public static State toRawType(@Nullable String pictureUrl) {
+ if (pictureUrl != null) {
+ RawType picture = HttpUtil.downloadImage(pictureUrl);
+ if (picture != null) {
+ return picture;
+ }
+ }
+ return UnDefType.UNDEF;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.utils;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This class holds various unit/measurement conversion methods
+ *
+ * @author Gaël L'hopital - Initial contribution
+ * @author Rob Nielsen - updated heat index
+ */
+@NonNullByDefault
+public class WeatherUtils {
+
+ /**
+ * Calculate the heat index using temperature and humidity
+ * https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
+ *
+ * @param temperature in (°C)
+ * @param humidity relative level (%)
+ * @return heatIndex in (°C)
+ */
+ public static double heatIndex(double temperature, double humidity) {
+ double tempF = (temperature * 9.0 / 5.0) + 32.0; // calculations are done in Fahrenheit
+ double heatIndex;
+ if (tempF >= 80.0) {
+ heatIndex = -42.379 + (2.04901523 * tempF) + (10.14333127 * humidity) - (0.22475541 * tempF * humidity)
+ - (0.00683783 * tempF * tempF) - (0.05481717 * humidity * humidity)
+ + (0.00122874 * tempF * tempF * humidity) + (0.00085282 * tempF * humidity * humidity)
+ - (0.00000199 * tempF * tempF * humidity * humidity);
+ if (humidity < 13.0 && tempF <= 112.0) {
+ heatIndex -= ((13.0 - humidity) / 4.0) * Math.sqrt((17.0 - Math.abs(tempF - 95.0)) / 17.0);
+ } else if (humidity > 85.0 && tempF <= 87.0) {
+ heatIndex += ((humidity - 85.0) / 10.0) * ((87.0 - tempF) / 5.0);
+ }
+ } else {
+ heatIndex = 0.5 * (tempF + 61.0 + ((tempF - 68.0) * 1.2) + (humidity * 0.094));
+ }
+
+ return (heatIndex - 32) * 5.0 / 9.0; // convert back to Celsius
+ }
+
+ public static double dewPointDep(double temperature, double dewpoint) {
+ return temperature - dewpoint;
+ }
+
+ /**
+ * Compute the Dewpoint temperature given temperature and hygrometry
+ * valid up to 60 degrees, from
+ * http://en.wikipedia.org/wiki/Dew_point#Calculating_the_dew_point
+ *
+ * @param temperature in (°C)
+ * @param humidity relative level (%)
+ * @return dewpoint temperature
+ */
+ public static double dewPoint(double temperature, double humidity) {
+ double a = 17.271, b = 237.2;
+ double gamma = ((a * temperature) / (b + temperature)) + Math.log(humidity / 100.0);
+ return b * gamma / (a - gamma);
+ }
+
+ /**
+ * Compute the Humidex index given temperature and hygrometry
+ *
+ * @param temperature in (°C)
+ * @param hygro relative level (%)
+ * @return Humidex index value
+ */
+ public static double humidex(double temperature, double hygro) {
+ double result = 6.112 * Math.pow(10, 7.5 * temperature / (237.7 + temperature)) * hygro / 100;
+ return temperature + 0.555555556 * (result - 10);
+ }
+
+ /**
+ * Compute the associated scale appreciation of a given humidex index
+ * https://www.researchgate.net/figure/The-scale-of-Humidex-and-the-degree-of-comfort_tbl1_335293174
+ *
+ * @param Humidex index value
+ * @return scale between 0 and 4
+ */
+ public static int humidexScale(double humidex) {
+ return humidex < 30 ? 0 : humidex < 40 ? 1 : humidex < 45 ? 2 : humidex < 55 ? 3 : 4;
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.webhook;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import com.google.gson.annotations.SerializedName;
-
-/**
- * The {@link NAWebhookCameraEvent} is responsible to hold
- * data given back by the Netatmo API when calling the webhook
- *
- * @author Gaël L'hopital - Initial contribution
- *
- */
-public class NAWebhookCameraEvent {
-
- public enum AppTypeEnum {
- @SerializedName("app_camera")
- CAMERA("camera");
-
- private String value;
-
- AppTypeEnum(String value) {
- this.value = value;
- }
-
- @Override
- public String toString() {
- return value;
- }
- }
-
- @SerializedName("app_type")
- private AppTypeEnum appType = null;
-
- public AppTypeEnum getAppType() {
- return appType;
- }
-
- public enum EventTypeEnum {
- @SerializedName("person")
- PERSON("person"),
-
- @SerializedName("person_away")
- PERSON_AWAY("person_away"),
-
- @SerializedName("movement")
- MOVEMENT("movement"),
-
- @SerializedName("outdoor")
- OUTDOOR("outdoor"),
-
- @SerializedName("connection")
- CONNECTION("connection"),
-
- @SerializedName("disconnection")
- DISCONNECTION("disconnection"),
-
- @SerializedName("on")
- ON("on"),
-
- @SerializedName("off")
- OFF("off"),
-
- @SerializedName("boot")
- BOOT("boot"),
-
- @SerializedName("sd")
- SD("sd"),
-
- @SerializedName("alim")
- ALIM("alim"),
-
- @SerializedName("daily_summary")
- DAILY_SUMMARY("daily_summary"),
-
- @SerializedName("new_module")
- NEW_MODULE("new_module"),
-
- @SerializedName("module_connect")
- MODULE_CONNECT("module_connect"),
-
- @SerializedName("module_disconnect")
- MODULE_DISCONNECT("module_disconnect"),
-
- @SerializedName("module_low_battery")
- MODULE_LOW_BATTERY("module_low_battery"),
-
- @SerializedName("module_end_update")
- MODULE_END_UPDATE("module_end_update"),
-
- @SerializedName("tag_big_move")
- TAG_BIG_MOVE("tag_big_move"),
-
- @SerializedName("tag_small_move")
- TAG_SMALL_MOVE("tag_small_move"),
-
- @SerializedName("tag_uninstalled")
- TAG_UNINSTALLED("tag_uninstalled"),
-
- @SerializedName("tag_open")
- TAG_OPEN("tag_open");
-
- private String value;
-
- EventTypeEnum(String value) {
- this.value = value;
- }
-
- @Override
- public String toString() {
- return value;
- }
- }
-
- @SerializedName("event_type")
- private EventTypeEnum eventType = null;
-
- public EventTypeEnum getEventType() {
- return eventType;
- }
-
- @SerializedName("camera_id")
- String cameraId;
-
- public String getCameraId() {
- return cameraId;
- }
-
- @SerializedName("home_id")
- String homeId;
-
- public String getHomeId() {
- return homeId;
- }
-
- @SerializedName("persons")
- private List<NAWebhookCameraEventPerson> persons = new ArrayList<>();
-
- public List<NAWebhookCameraEventPerson> getPersons() {
- return persons;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 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;
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.netatmo.internal.webhook;
+
+import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Scanner;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriBuilderException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.netatmo.internal.api.NetatmoException;
+import org.openhab.binding.netatmo.internal.api.SecurityApi;
+import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent;
+import org.openhab.binding.netatmo.internal.deserialization.NADeserializer;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
+import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * HTTP servlet for Netatmo Webhook.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class NetatmoServlet extends HttpServlet {
+ private static final long serialVersionUID = -354583910860541214L;
+ private static final String CALLBACK_URI = "/" + BINDING_ID;
+
+ private final Logger logger = LoggerFactory.getLogger(NetatmoServlet.class);
+ private final Map<String, EventCapability> dataListeners = new ConcurrentHashMap<>();
+ private final HttpService httpService;
+ private final NADeserializer deserializer;
+ private final Optional<SecurityApi> securityApi;
+ private boolean hookSet = false;
+
+ public NetatmoServlet(HttpService httpService, ApiBridgeHandler apiBridge, String webHookUrl) {
+ this.httpService = httpService;
+ this.deserializer = apiBridge.getDeserializer();
+ this.securityApi = Optional.ofNullable(apiBridge.getRestManager(SecurityApi.class));
+ securityApi.ifPresent(api -> {
+ try {
+ httpService.registerServlet(CALLBACK_URI, this, null, httpService.createDefaultHttpContext());
+ logger.debug("Started Netatmo Webhook Servlet at '{}'", CALLBACK_URI);
+ URI uri = UriBuilder.fromUri(webHookUrl).path(BINDING_ID).build();
+ try {
+ logger.info("Setting Netatmo Welcome WebHook to {}", uri.toString());
+ api.addwebhook(uri);
+ hookSet = true;
+ } catch (UriBuilderException e) {
+ logger.info("webhookUrl is not a valid URI '{}' : {}", uri, e.getMessage());
+ } catch (NetatmoException e) {
+ logger.info("Error setting webhook : {}", e.getMessage());
+ }
+ } catch (ServletException | NamespaceException e) {
+ logger.warn("Could not start Netatmo Webhook Servlet : {}", e.getMessage());
+ }
+ });
+ }
+
+ public void dispose() {
+ securityApi.ifPresent(api -> {
+ if (hookSet) {
+ logger.info("Releasing Netatmo Welcome WebHook");
+ try {
+ api.dropWebhook();
+ } catch (NetatmoException e) {
+ logger.warn("Error releasing webhook : {}", e.getMessage());
+ }
+ }
+ httpService.unregister(CALLBACK_URI);
+ });
+ logger.debug("Netatmo Webhook Servlet stopped");
+ }
+
+ @Override
+ protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
+ if (req != null && resp != null) {
+ String data = inputStreamToString(req.getInputStream());
+ if (!data.isEmpty()) {
+ logger.debug("Event transmitted from restService : {}", data);
+ try {
+ WebhookEvent event = deserializer.deserialize(WebhookEvent.class, data);
+ List<String> tobeNotified = collectNotified(event);
+ dataListeners.keySet().stream().filter(tobeNotified::contains).forEach(id -> {
+ EventCapability module = dataListeners.get(id);
+ if (module != null) {
+ module.setNewData(event);
+ }
+ });
+ } catch (NetatmoException e) {
+ logger.info("Error deserializing webhook data received : {}. {}", data, e.getMessage());
+ }
+ }
+ resp.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ resp.setContentType(MediaType.APPLICATION_JSON);
+ resp.setHeader("Access-Control-Allow-Origin", "*");
+ resp.setHeader("Access-Control-Allow-Methods", HttpMethod.POST);
+ resp.setIntHeader("Access-Control-Max-Age", 3600);
+ resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
+ resp.getWriter().write("");
+ }
+ }
+
+ private List<String> collectNotified(WebhookEvent event) {
+ List<String> result = new ArrayList<>();
+ result.add(event.getCameraId());
+ String person = event.getPersonId();
+ if (person != null) {
+ result.add(person);
+ }
+ result.addAll(event.getPersons().keySet());
+ return result.stream().distinct().collect(Collectors.toList());
+ }
+
+ public void registerDataListener(String id, EventCapability dataListener) {
+ dataListeners.put(id, dataListener);
+ }
+
+ public void unregisterDataListener(EventCapability dataListener) {
+ dataListeners.entrySet().removeIf(entry -> entry.getValue().equals(dataListener));
+ }
+
+ private String inputStreamToString(InputStream is) throws IOException {
+ String value = "";
+ try (Scanner scanner = new Scanner(is)) {
+ scanner.useDelimiter("\\A");
+ value = scanner.hasNext() ? scanner.next() : "";
+ }
+ return value;
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 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;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 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);
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.welcome;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Calendar;
-import java.util.Comparator;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.function.Function;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
-import org.openhab.binding.netatmo.internal.camera.CameraHandler;
-import org.openhab.binding.netatmo.internal.handler.AbstractNetatmoThingHandler;
-import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
-import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.swagger.client.model.NAWelcomeEvent;
-import io.swagger.client.model.NAWelcomeHome;
-import io.swagger.client.model.NAWelcomePlace;
-import io.swagger.client.model.NAWelcomeSnapshot;
-import io.swagger.client.model.NAWelcomeSubEvent;
-
-/**
- * {@link NAWelcomeHomeHandler} is the class used to handle the Welcome Home Data
- *
- * @author Gaël L'hopital - Initial contribution
- * @author Ing. Peter Weiss - Welcome camera implementation
- *
- */
-@NonNullByDefault
-public class NAWelcomeHomeHandler extends NetatmoDeviceHandler<NAWelcomeHome> {
- private final Logger logger = LoggerFactory.getLogger(NAWelcomeHomeHandler.class);
-
- private int iPersons = -1;
- private int iUnknowns = -1;
- private @Nullable NAWelcomeEvent lastEvent;
- private boolean isNewLastEvent;
- private @Nullable Integer dataTimeStamp;
-
- public NAWelcomeHomeHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- protected Optional<NAWelcomeHome> updateReadings() {
- Optional<NAWelcomeHome> result = getBridgeHandler().flatMap(handler -> handler.getWelcomeDataBody(getId()))
- .map(dataBody -> nonNullStream(dataBody.getHomes())
- .filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
- // data time stamp is updated to now as WelcomeDataBody does not provide any information according to this need
- dataTimeStamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
- result.ifPresent(home -> {
- nonNullList(home.getCameras()).forEach(camera -> childs.put(camera.getId(), camera));
-
- // Check how many persons are at home
- iPersons = 0;
- iUnknowns = 0;
-
- logger.debug("welcome home '{}' calculate Persons at home count", getId());
- nonNullList(home.getPersons()).forEach(person -> {
- iPersons += person.isOutOfSight() ? 0 : 1;
- if (person.getPseudo() != null) {
- childs.put(person.getId(), person);
- } else {
- iUnknowns += person.isOutOfSight() ? 0 : 1;
- }
- });
-
- NAWelcomeEvent previousLastEvent = lastEvent;
- lastEvent = nonNullStream(home.getEvents()).max(Comparator.comparingInt(NAWelcomeEvent::getTime))
- .orElse(null);
- isNewLastEvent = previousLastEvent != null && !previousLastEvent.equals(lastEvent);
- });
- return result;
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- Optional<NAWelcomeEvent> lastEvt = getLastEvent();
- switch (channelId) {
- case CHANNEL_WELCOME_HOME_CITY:
- return getPlaceInfo(NAWelcomePlace::getCity);
- case CHANNEL_WELCOME_HOME_COUNTRY:
- return getPlaceInfo(NAWelcomePlace::getCountry);
- case CHANNEL_WELCOME_HOME_TIMEZONE:
- return getPlaceInfo(NAWelcomePlace::getTimezone);
- case CHANNEL_WELCOME_HOME_PERSONCOUNT:
- return iPersons != -1 ? new DecimalType(iPersons) : UnDefType.UNDEF;
- case CHANNEL_WELCOME_HOME_UNKNOWNCOUNT:
- return iUnknowns != -1 ? new DecimalType(iUnknowns) : UnDefType.UNDEF;
- case CHANNEL_WELCOME_EVENT_TYPE:
- return lastEvt.map(e -> toStringType(e.getType())).orElse(UnDefType.UNDEF);
- case CHANNEL_WELCOME_EVENT_TIME:
- return lastEvt.map(e -> toDateTimeType(e.getTime(), timeZoneProvider.getTimeZone()))
- .orElse(UnDefType.UNDEF);
- case CHANNEL_WELCOME_EVENT_CAMERAID:
- if (lastEvt.isPresent()) {
- return findNAThing(lastEvt.get().getCameraId()).map(c -> toStringType(c.getThing().getLabel()))
- .orElse(UnDefType.UNDEF);
- } else {
- return UnDefType.UNDEF;
- }
- case CHANNEL_WELCOME_EVENT_PERSONID:
- if (lastEvt.isPresent()) {
- return findNAThing(lastEvt.get().getPersonId()).map(p -> toStringType(p.getThing().getLabel()))
- .orElse(UnDefType.UNDEF);
- } else {
- return UnDefType.UNDEF;
- }
- case CHANNEL_WELCOME_EVENT_SNAPSHOT:
- return findSnapshotURL().map(url -> toRawType(url)).orElse(UnDefType.UNDEF);
- case CHANNEL_WELCOME_EVENT_SNAPSHOT_URL:
- return findSnapshotURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
- case CHANNEL_WELCOME_EVENT_VIDEO_URL:
- if (lastEvt.isPresent() && lastEvt.get().getVideoId() != null) {
- String cameraId = lastEvt.get().getCameraId();
- Optional<AbstractNetatmoThingHandler> thing = findNAThing(cameraId);
- if (thing.isPresent()) {
- CameraHandler eventCamera = (CameraHandler) thing.get();
- Optional<String> streamUrl = eventCamera.getStreamURL(lastEvt.get().getVideoId());
- if (streamUrl.isPresent()) {
- return new StringType(streamUrl.get());
- }
- }
- }
- return UnDefType.UNDEF;
- case CHANNEL_WELCOME_EVENT_VIDEOSTATUS:
- return lastEvt.map(e -> e.getVideoId() != null ? toStringType(e.getVideoStatus()) : UnDefType.UNDEF)
- .orElse(UnDefType.UNDEF);
- case CHANNEL_WELCOME_EVENT_ISARRIVAL:
- return lastEvt.map(e -> toOnOffType(e.isIsArrival())).orElse(UnDefType.UNDEF);
- case CHANNEL_WELCOME_EVENT_MESSAGE:
- return findEventMessage().map(m -> (State) new StringType(m.replace("<b>", "").replace("</b>", "")))
- .orElse(UnDefType.UNDEF);
- case CHANNEL_WELCOME_EVENT_SUBTYPE:
- return lastEvt.map(e -> toDecimalType(e.getSubType())).orElse(UnDefType.UNDEF);
- }
- return super.getNAThingProperty(channelId);
- }
-
- @Override
- protected void triggerChannelIfRequired(String channelId) {
- if (isNewLastEvent) {
- if (CHANNEL_CAMERA_EVENT.equals(channelId)) {
- findDetectedObjectTypes(getLastEvent())
- .forEach(detectedType -> triggerChannel(channelId, detectedType));
- }
- }
- super.triggerChannelIfRequired(channelId);
- }
-
- private static Set<String> findDetectedObjectTypes(Optional<NAWelcomeEvent> eventOptional) {
- Set<String> detectedObjectTypes = new TreeSet<>();
- if (!eventOptional.isPresent()) {
- return detectedObjectTypes;
- }
-
- NAWelcomeEvent event = eventOptional.get();
-
- if (NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.toString().equals(event.getType())) {
- if (event.getPersonId() != null) {
- detectedObjectTypes.add(NAWelcomeSubEvent.TypeEnum.HUMAN.name());
- } else {
- Optional<NAWelcomeSubEvent.TypeEnum> detectedCategory = findDetectedCategory(event);
- if (detectedCategory.isPresent()) {
- detectedObjectTypes.add(detectedCategory.get().name());
- } else {
- detectedObjectTypes.add(NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.name());
- }
- }
- }
-
- nonNullList(event.getEventList()).forEach(subEvent -> {
- String detectedObjectType = subEvent.getType().name();
- detectedObjectTypes.add(detectedObjectType);
- });
- return detectedObjectTypes;
- }
-
- private static Optional<NAWelcomeSubEvent.TypeEnum> findDetectedCategory(NAWelcomeEvent event) {
- NAWelcomeEvent.CategoryEnum category = event.getCategory();
- if (category != null) {
- // It is safe to convert the enum, both enums have the same values.
- return Optional.of(NAWelcomeSubEvent.TypeEnum.valueOf(category.name()));
- }
- return Optional.empty();
- }
-
- private Optional<String> findEventMessage() {
- Optional<NAWelcomeEvent> lastEvt = getLastEvent();
- if (lastEvt.isPresent()) {
- @Nullable
- String message = lastEvt.get().getMessage();
- if (message != null) {
- return Optional.of(message);
- }
-
- return lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getMessage);
- }
- return Optional.empty();
- }
-
- /**
- * Returns the Url of the picture
- *
- * @return Url of the picture or null
- */
- protected Optional<String> findSnapshotURL() {
- Optional<NAWelcomeEvent> lastEvt = getLastEvent();
- if (lastEvt.isPresent()) {
- @Nullable
- NAWelcomeSnapshot snapshot = lastEvt.get().getSnapshot();
- if (snapshot == null) {
- snapshot = lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getSnapshot).orElse(null);
- }
-
- if (snapshot != null && snapshot.getId() != null && snapshot.getKey() != null) {
- return Optional.of(WELCOME_PICTURE_URL + "?" + WELCOME_PICTURE_IMAGEID + "=" + snapshot.getId() + "&"
- + WELCOME_PICTURE_KEY + "=" + snapshot.getKey());
- } else {
- logger.debug("Unable to build snapshot url for Home : {}", getId());
- }
- }
- return Optional.empty();
- }
-
- @Override
- protected Optional<Integer> getDataTimestamp() {
- Integer timestamp = dataTimeStamp;
- return timestamp != null ? Optional.of(timestamp) : Optional.empty();
- }
-
- private State getPlaceInfo(Function<NAWelcomePlace, String> infoGetFunction) {
- return getDevice().map(d -> toStringType(infoGetFunction.apply(d.getPlace()))).orElse(UnDefType.UNDEF);
- }
-
- private Optional<NAWelcomeSubEvent> findFirstSubEvent(NAWelcomeEvent event) {
- return Optional.ofNullable(event).map(NAWelcomeEvent::getEventList)
- .flatMap(subEvents -> nonNullStream(subEvents).findFirst());
- }
-
- private Optional<NAWelcomeEvent> getLastEvent() {
- NAWelcomeEvent evt = lastEvent;
- return evt != null ? Optional.of(evt) : Optional.empty();
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.welcome;
-
-import static org.openhab.binding.netatmo.internal.APIUtils.*;
-import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
-import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
-
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
-import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-import io.swagger.client.api.WelcomeApi;
-import io.swagger.client.model.NAWelcomeEvent;
-import io.swagger.client.model.NAWelcomeEventResponse;
-import io.swagger.client.model.NAWelcomeFace;
-import io.swagger.client.model.NAWelcomePerson;
-
-/**
- * {@link NAWelcomePersonHandler} is the class used to handle the Welcome Home Data
- *
- * @author Ing. Peter Weiss - Initial contribution
- *
- */
-@NonNullByDefault
-public class NAWelcomePersonHandler extends NetatmoModuleHandler<NAWelcomePerson> {
- private @Nullable String avatarURL;
- private @Nullable NAWelcomeEvent lastEvent;
-
- public NAWelcomePersonHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
- super(thing, timeZoneProvider);
- }
-
- @Override
- public void updateChannels(Object module) {
- if (isRefreshRequired()) {
- getApi().ifPresent(api -> {
- NAWelcomeEventResponse eventResponse = api.getlasteventof(getParentId(), getId(), 10);
-
- // Search the last event for this person
- nonNullList(eventResponse.getBody().getEventsList()).forEach(event -> {
- if (event.getPersonId() != null && event.getPersonId().equalsIgnoreCase(getId())
- && (lastEvent == null || lastEvent.getTime() < event.getTime())) {
- lastEvent = event;
- }
- });
- });
-
- setRefreshRequired(false);
- }
- super.updateChannels(module);
- }
-
- @Override
- protected State getNAThingProperty(String channelId) {
- Optional<NAWelcomeEvent> lastEvt = getLastEvent();
- String url;
- switch (channelId) {
- case CHANNEL_WELCOME_PERSON_LASTSEEN:
- return getModule().map(m -> toDateTimeType(m.getLastSeen(), timeZoneProvider.getTimeZone()))
- .orElse(UnDefType.UNDEF);
- case CHANNEL_WELCOME_PERSON_ATHOME:
- return getModule().map(m -> m.isOutOfSight() != null ? toOnOffType(!m.isOutOfSight()) : UnDefType.UNDEF)
- .orElse(UnDefType.UNDEF);
- case CHANNEL_WELCOME_PERSON_AVATAR_URL:
- return toStringType(getAvatarURL());
- case CHANNEL_WELCOME_PERSON_AVATAR:
- url = getAvatarURL();
- return url != null ? toRawType(url) : UnDefType.UNDEF;
- case CHANNEL_WELCOME_PERSON_LASTMESSAGE:
- return (lastEvt.isPresent() && lastEvt.get().getMessage() != null)
- ? toStringType(lastEvt.get().getMessage().replace("<b>", "").replace("</b>", ""))
- : UnDefType.UNDEF;
- case CHANNEL_WELCOME_PERSON_LASTTIME:
- return lastEvt.isPresent() ? toDateTimeType(lastEvt.get().getTime(), timeZoneProvider.getTimeZone())
- : UnDefType.UNDEF;
- case CHANNEL_WELCOME_PERSON_LASTEVENT:
- url = getLastEventURL();
- return url != null ? toRawType(url) : UnDefType.UNDEF;
- case CHANNEL_WELCOME_PERSON_LASTEVENT_URL:
- return getLastEventURL() != null ? toStringType(getLastEventURL()) : UnDefType.UNDEF;
- }
- return super.getNAThingProperty(channelId);
- }
-
- private @Nullable String getLastEventURL() {
- Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
- Optional<NAWelcomeEvent> lastEvt = getLastEvent();
- if (handler.isPresent() && lastEvt.isPresent() && lastEvt.get().getSnapshot() != null) {
- return handler.get().getPictureUrl(lastEvt.get().getSnapshot().getId(),
- lastEvt.get().getSnapshot().getKey());
- }
- return null;
- }
-
- private @Nullable String getAvatarURL() {
- Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
- Optional<NAWelcomePerson> person = getModule();
- if (handler.isPresent() && avatarURL == null && person.isPresent()) {
- NAWelcomeFace face = person.get().getFace();
- if (face != null) {
- avatarURL = handler.get().getPictureUrl(face.getId(), face.getKey());
- }
- }
- return avatarURL;
- }
-
- @Override
- public void handleCommand(ChannelUID channelUID, Command command) {
- super.handleCommand(channelUID, command);
- if ((command instanceof OnOffType) && (CHANNEL_WELCOME_PERSON_ATHOME.equalsIgnoreCase(channelUID.getId()))) {
- getApi().ifPresent(api -> {
- if ((OnOffType) command == OnOffType.OFF) {
- api.setpersonsaway(getParentId(), getId());
- } else {
- api.setpersonshome(getParentId(), "[\"" + getId() + "\"]");
- }
- invalidateParentCacheAndRefresh();
- });
- }
- }
-
- private Optional<WelcomeApi> getApi() {
- return getBridgeHandler().flatMap(handler -> handler.getWelcomeApi());
- }
-
- private Optional<NAWelcomeEvent> getLastEvent() {
- NAWelcomeEvent evt = lastEvent;
- return evt != null ? Optional.of(evt) : Optional.empty();
- }
-}
<description>The Netatmo binding integrates Weather Station with companion modules, Healthy Home Coach, Thermostat Plug
and Welcome Camera.</description>
+
<config-description>
- <parameter name="backgroundDiscovery" type="boolean" required="false">
- <label>Background Discovery</label>
- <description>If set to true, the device and its associated modules are updated in the discovery inbox at each API
- call run to refresh device data. Default is false.</description>
+ <parameter name="features" type="text" required="true" multiple="true">
+ <label>Feature Area</label>
+ <description>Defines the set of capabilities you want to operate on.</description>
+ <options>
+ <option value="AIR_CARE">Air Care</option>
+ <option value="WEATHER">Weather</option>
+ <option value="ENERGY">Energy</option>
+ <option value="SECURITY">Security</option>
+ </options>
+ <limitToOptions>true</limitToOptions>
+ <default>WEATHER</default>
+ </parameter>
+
+ <parameter name="readFriends" type="boolean">
+ <label>Access Guests</label>
+ <description>For Weather Stations: A friend gave you access to their Netatmo Weather Station.</description>
<default>false</default>
</parameter>
</config-description>
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
- <config-description uri="thing-type:netatmo:bridge">
+ <config-description uri="netatmo:api_bridge">
<parameter name="clientId" type="text" required="true">
<label>Client ID</label>
<description>Client ID provided for the application you created on http://dev.netatmo.com/createapp</description>
<parameter name="clientSecret" type="text" required="true">
<label>Client Secret</label>
- <description>Client Secret provided for the application you created</description>
+ <description>Client Secret provided for the application you created.</description>
<context>password</context>
</parameter>
<parameter name="username" type="text" required="true">
<label>Username</label>
- <description>Your Netatmo API username (email)</description>
+ <description>Your Netatmo API username (email).</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
- <description>Your Netatmo API password</description>
+ <description>Your Netatmo API password.</description>
<context>password</context>
</parameter>
- <parameter name="readStation" type="boolean">
- <label>Access Weather Station</label>
- <description>Read weather station's data.</description>
- <default>true</default>
- </parameter>
-
- <parameter name="readHealthyHomeCoach" type="boolean">
- <label>Access Healthy Home Coach</label>
- <description>Read healthy home coach's data.</description>
- <default>false</default>
- </parameter>
-
- <parameter name="readThermostat" type="boolean">
- <label>Access Thermostat</label>
- <description>Read and Write thermostat's data.</description>
- <default>false</default>
- </parameter>
-
- <parameter name="readWelcome" type="boolean">
- <label>Access Welcome Camera</label>
- <description>Read and Access Welcome camera's data.</description>
- <default>false</default>
- </parameter>
-
- <parameter name="readPresence" type="boolean">
- <label>Access Presence Camera</label>
- <description>Read and Access Presence camera's data.</description>
- <default>false</default>
- </parameter>
-
- <parameter name="webHookUrl" type="text">
+ <parameter name="webHookUrl" type="text" required="false">
<label>Webhook Address</label>
- <description>Protocol, public IP and port to access OH2 server from Internet.</description>
- <advanced>true</advanced>
+ <description>Protocol, public IP and port to access openHAB server from Internet.</description>
</parameter>
<parameter name="reconnectInterval" type="integer" unit="s">
<label>Reconnect Interval</label>
<description>The reconnection interval to Netatmo API (in s).</description>
- <default>5400</default>
- <advanced>true</advanced>
+ <default>300</default>
</parameter>
-
</config-description>
- <config-description uri="thing-type:netatmo:station">
- <parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
- <label>Equipment ID</label>
- <description>Id of the Device (mac address)</description>
+ <config-description uri="netatmo:sum_rain:config">
+ <parameter name="period" type="text">
+ <label>Period</label>
+ <description>Observation period for summing rain quantities.</description>
+ <options>
+ <option value="30min">30 minutes</option>
+ <option value="1hour">1 hour</option>
+ <option value="3hours">3 hours</option>
+ <option value="1day">This day</option>
+ <option value="1week">This week</option>
+ <option value="1month">This month</option>
+ </options>
+ <default>1week</default>
</parameter>
</config-description>
- <config-description uri="thing-type:netatmo:plug">
- <parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
- <label>Equipment ID</label>
- <description>Id of the Device (mac address)</description>
+ <config-description uri="netatmo:measure:config">
+ <parameter name="limit" type="text">
+ <label>Boundary</label>
+ <description>Defines the requested boundary.</description>
+ <options>
+ <option value="MIN">Minimum</option>
+ <option value="MAX">Maximum</option>
+ </options>
+ <default>MIN</default>
+ </parameter>
+ <parameter name="period" type="text">
+ <label>Period</label>
+ <description>Observation period for searched boundary.</description>
+ <options>
+ <option value="30min">30 minutes</option>
+ <option value="1hour">1 hour</option>
+ <option value="3hours">3 hours</option>
+ <option value="1day">This day</option>
+ <option value="1week">This week</option>
+ <option value="1month">This month</option>
+ </options>
+ <default>1week</default>
</parameter>
</config-description>
- <config-description uri="thing-type:netatmo:module">
- <parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
- <label>Module ID</label>
- <description>Id of the Module</description>
- </parameter>
-
- <parameter name="parentId" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
- <label>Device ID</label>
- <description>Id of the main device</description>
+ <config-description uri="netatmo:timestamp:config">
+ <parameter name="limit" type="text">
+ <label>Boundary</label>
+ <description>Defines the requested boundary.</description>
+ <options>
+ <option value="DATE_MIN">Timestamp of minimum</option>
+ <option value="DATE_MAX">Timestamp of maximum</option>
+ </options>
+ <default>DATE_MIN</default>
+ </parameter>
+ <parameter name="period" type="text">
+ <label>Period</label>
+ <description>Observation period for the queried value.</description>
+ <options>
+ <option value="1week">This week</option>
+ <option value="1month">This month</option>
+ </options>
+ <default>1week</default>
</parameter>
</config-description>
- <config-description uri="thing-type:netatmo:natherm1">
+ <config-description uri="netatmo:device">
<parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
- <label>Module ID</label>
- <description>Id of the Module</description>
- </parameter>
-
- <parameter name="parentId" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
- <label>Device ID</label>
- <description>Id of the main device</description>
+ <label>Equipment ID</label>
+ <description>ID of the device (MAC address).</description>
</parameter>
+ </config-description>
- <parameter name="setpointDefaultDuration" type="integer">
- <label>Setpoint Duration</label>
- <description>Default duration of thermostat change when force to max or manual.</description>
- <default>60</default>
- <advanced>true</advanced>
+ <config-description uri="netatmo:virtual">
+ <parameter name="id" type="text" required="true">
+ <label>Thing ID</label>
+ <description>Unique identifier of the thing defined by Netatmo.</description>
</parameter>
</config-description>
- <config-description uri="thing-type:netatmo:welcomehome">
+ <config-description uri="netatmo:home">
<parameter name="id" type="text" required="true">
- <label>Home ID</label>
- <description>UUID of the home</description>
+ <label>Thing ID</label>
+ <description>Unique identifier of the thing defined by Netatmo.</description>
</parameter>
- <parameter name="refreshInterval" type="integer" min="2000" unit="ms">
+ <parameter name="refreshInterval" type="integer" min="20" unit="s">
<label>Refresh Interval</label>
- <description>The refresh interval to poll Netatmo API (in ms).</description>
- <default>300000</default>
- <advanced>true</advanced>
+ <description>The refresh interval to poll Netatmo API (in seconds).</description>
+ <default>180</default>
</parameter>
</config-description>
- <config-description uri="thing-type:netatmo:camera">
+ <config-description uri="netatmo:configurable">
<parameter name="id" type="text" pattern="([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" required="true">
- <label>Camera ID</label>
- <description>Camera MAC Address</description>
- </parameter>
-
- <parameter name="parentId" type="text" required="true">
- <label>Home ID</label>
- <description>UUID of the home hosting the camera</description>
- </parameter>
- </config-description>
-
- <config-description uri="thing-type:netatmo:nawelcomeperson">
- <parameter name="id" type="text" required="true">
- <label>Person ID</label>
- <description>UUID of the Person</description>
+ <label>Equipment ID</label>
+ <description>ID of the device (MAC address).</description>
</parameter>
- <parameter name="parentId" type="text" required="true">
- <label>Home ID</label>
- <description>UUID of the home</description>
+ <parameter name="refreshInterval" type="integer" min="20" unit="s">
+ <label>Refresh Interval</label>
+ <description>The refresh interval to poll Netatmo API (in seconds).</description>
+ <default>180</default>
</parameter>
</config-description>
--- /dev/null
+# 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.
+++ /dev/null
-# 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
+++ /dev/null
-# 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
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="netatmo"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="airquality-extended">
+ <label>Air Quality</label>
+ <channels>
+ <channel id="co2" typeId="co2"/>
+ <channel id="health-index" typeId="health-index"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="temperature">
+ <label>Temperature</label>
+ <channels>
+ <channel id="value" typeId="system.indoor-temperature"/>
+ <channel id="min-today" typeId="min-temp"/>
+ <channel id="max-today" typeId="max-temp"/>
+ <channel id="min-time" typeId="timestamp-advanced">
+ <label>Today Min Timestamp</label>
+ <description>Moment when temperature was measured at its minimum today.</description>
+ </channel>
+ <channel id="max-time" typeId="timestamp-advanced">
+ <label>Today Max Timestamp</label>
+ <description>Moment when temperature was measured at its maximum today.</description>
+ </channel>
+ <channel id="heat-index" typeId="heat-index"/>
+ <channel id="dewpoint" typeId="dewpoint"/>
+ <channel id="dewpoint-depression" typeId="dewpoint-depression"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="pressure">
+ <label>Pressure</label>
+ <channels>
+ <channel id="value" typeId="system.barometric-pressure"/>
+ <channel id="absolute" typeId="absolute-pressure"/>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
- xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
- <!-- Netatmo API Bridge -->
- <bridge-type id="netatmoapi">
- <label>Netatmo API</label>
- <description>This bridge represents the gateway to Netatmo API.</description>
- <config-description-ref uri="thing-type:netatmo:bridge"/>
- </bridge-type>
-
-</thing:thing-descriptions>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
- xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
- <thing-type id="NACamera">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Welcome Camera</label>
- <description>This represents a welcome camera at home</description>
-
- <channels>
- <channel id="welcomeCameraStatus" typeId="status"></channel>
- <channel id="welcomeCameraSdStatus" typeId="sd_status"></channel>
- <channel id="welcomeCameraAlimStatus" typeId="alim_status"></channel>
- <channel id="welcomeCameraIsLocal" typeId="is_locale"></channel>
- <channel id="welcomeCameraLivePicture" typeId="live_picture"></channel>
- <channel id="welcomeCameraLivePictureUrl" typeId="live_picture_url"></channel>
- <channel id="welcomeCameraLiveStreamUrl" typeId="live_stream_url"></channel>
- <channel id="welcomeHomeEvent" typeId="homeEvent"></channel>
- </channels>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:camera"/>
- </thing-type>
-
- <thing-type id="NOC">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Presence Camera</label>
- <description>This represents a presence camera at home</description>
-
- <channels>
- <channel id="cameraStatus" typeId="status"></channel>
- <channel id="cameraSdStatus" typeId="sd_status"></channel>
- <channel id="cameraAlimStatus" typeId="alim_status"></channel>
- <channel id="cameraIsLocal" typeId="is_locale"></channel>
- <channel id="cameraLivePicture" typeId="live_picture"></channel>
- <channel id="cameraLivePictureUrl" typeId="live_picture_url"></channel>
- <channel id="cameraLiveStreamUrl" typeId="live_stream_url"></channel>
- <channel id="cameraFloodlightAutoMode" typeId="floodlightAutoMode"></channel>
- <channel id="cameraFloodlight" typeId="floodlight"></channel>
- </channels>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:camera"/>
- </thing-type>
-
-
- <channel-type id="status">
- <item-type>Switch</item-type>
- <label>State</label>
- <description>State of the camera</description>
- </channel-type>
-
- <channel-type id="sd_status">
- <item-type>Switch</item-type>
- <label>SD State</label>
- <description>State of the SD card</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="alim_status">
- <item-type>Switch</item-type>
- <label>Alim State</label>
- <description>State of the power connector</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="is_locale" advanced="true">
- <item-type>Switch</item-type>
- <label>Is Local</label>
- <description>Only for scope access_camera. If Camera and application requesting the information are on the same
- network (true/false)</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="live_picture_url" advanced="true">
- <item-type>String</item-type>
- <label>Live Snapshot URL</label>
- <description>Url of the live snapshot for this camera (need scope access_camera)</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="live_picture">
- <item-type>Image</item-type>
- <label>Live Snapshot</label>
- <description>Camera Live Snapshot</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="live_stream_url" advanced="true">
- <item-type>String</item-type>
- <label>Live Stream URL</label>
- <description>Url of the live stream for this camera</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="floodlightAutoMode">
- <item-type>Switch</item-type>
- <label>Floodlight Auto-Mode</label>
- <description>State of the floodlight auto-mode</description>
- </channel-type>
-
- <channel-type id="floodlight">
- <item-type>Switch</item-type>
- <label>Floodlight</label>
- <description>State of the floodlight</description>
- </channel-type>
-
-</thing:thing-descriptions>
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
- <channel-type id="lastStatusStore" advanced="true">
- <item-type>DateTime</item-type>
- <label>Last Status Store</label>
- <description>Last Status Store</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="location">
- <item-type>Location</item-type>
- <label>Location</label>
- <description>Location of the device</description>
- <state readOnly="true" pattern="%2$s°N,%3$s°W, %1$s m"/>
- </channel-type>
-
- <channel-type id="temperature">
- <item-type>Number:Temperature</item-type>
- <label>Temperature</label>
- <description>Current temperature</description>
- <category>Temperature</category>
- <state readOnly="true" pattern="%.1f %unit%"/>
- </channel-type>
-
- <channel-type id="minTemp" advanced="true">
- <item-type>Number:Temperature</item-type>
- <label>Min Temp</label>
- <description>Minimum Temperature on current day</description>
- <category>Temperature</category>
- <state readOnly="true" pattern="%.1f %unit%"/>
- </channel-type>
-
- <channel-type id="minTempThisWeek" advanced="true">
- <item-type>Number:Temperature</item-type>
- <label>Min Temp This Week</label>
- <description>Minimum Temperature this week</description>
- <category>Temperature</category>
- <state readOnly="true" pattern="%.1f %unit%"/>
- </channel-type>
-
- <channel-type id="minTempThisMonth" advanced="true">
- <item-type>Number:Temperature</item-type>
- <label>Min Temp This Month</label>
- <description>Minimum Temperature this month</description>
- <category>Temperature</category>
- <state readOnly="true" pattern="%.1f %unit%"/>
- </channel-type>
-
- <channel-type id="maxTemp" advanced="true">
- <item-type>Number:Temperature</item-type>
- <label>Max Temp</label>
- <description>Maximum Temperature on current day</description>
- <category>Temperature</category>
- <state readOnly="true" pattern="%.1f %unit%"/>
- </channel-type>
-
- <channel-type id="maxTempThisWeek" advanced="true">
- <item-type>Number:Temperature</item-type>
- <label>Max Temp This Week</label>
- <description>Maximum Temperature this week</description>
- <category>Temperature</category>
- <state readOnly="true" pattern="%.1f %unit%"/>
- </channel-type>
-
- <channel-type id="maxTempThisMonth" advanced="true">
- <item-type>Number:Temperature</item-type>
- <label>Max Temp This Month</label>
- <description>Maximum Temperature this month</description>
- <category>Temperature</category>
- <state readOnly="true" pattern="%.1f %unit%"/>
- </channel-type>
-
- <channel-type id="temperatureTrend" advanced="true">
- <item-type>String</item-type>
- <label>Temp Trend</label>
- <description>Temperature Evolution Trend</description>
- <category>Temperature</category>
- <state readOnly="true" pattern="%s">
- <options>
- <option value="up">up</option>
- <option value="stable">stable</option>
- <option value="down">down</option>
- </options>
- </state>
- </channel-type>
-
- <channel-type id="setpointTemp">
- <item-type>Number:Temperature</item-type>
- <label>Setpoint</label>
- <description>Thermostat temperature setpoint</description>
- <category>Temperature</category>
- <state pattern="%.1f %unit%" readOnly="false"/>
- </channel-type>
-
- <channel-type id="setpointMode">
- <item-type>String</item-type>
- <label>Setpoint Mode</label>
- <description>Chosen setpoint_mode (program, away, hg, manual, off, max)</description>
- <state readOnly="false">
- <options>
- <option value="program">Following a weekly schedule</option>
- <option value="away">Applying the -away- temperature as defined by the user</option>
- <option value="hg">Frost-guard</option>
- <option value="manual">Applying a manually set temperature setpoint</option>
- <option value="off">Currently off</option>
- <option value="max">Heating continuously</option>
- </options>
- </state>
- </channel-type>
-
- <channel-type id="ThermRelayCmd" advanced="false">
+ <channel-type id="monitoring-status">
<item-type>Switch</item-type>
- <label>Heating Status</label>
- <description>Indicates whether the furnace is heating or not</description>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="ThermOrientation" advanced="true">
- <item-type>Number</item-type>
- <label>Orientation</label>
- <description>Physical orientation of the thermostat module</description>
- <state readOnly="true" pattern="%d"/>
- </channel-type>
-
- <channel-type id="timeUtc" advanced="true">
- <item-type>DateTime</item-type>
- <label>Measurement Time</label>
- <description>Timestamp when data was measured</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <label>Monitoring</label>
+ <description>Monitoring state of the camera</description>
</channel-type>
- <channel-type id="lastPlugSeen" advanced="true">
- <item-type>DateTime</item-type>
- <label>Last Plug Seen</label>
- <description>Last Plug Seen</description>
- <category>Time</category>
+ <channel-type id="window-open">
+ <item-type>Contact</item-type>
+ <label>Window Status</label>
+ <description>Windows of the room are opened.</description>
+ <category>Window</category>
<state readOnly="true"/>
</channel-type>
- <channel-type id="dateMinCo2" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min CO2</label>
- <description>Date when minimum CO2 was reached on current day</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMinCo2ThisWeek" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min CO2 This Week</label>
- <description>Date when minimum CO2 was reached this week</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMinCo2ThisMonth" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min CO2 This Month</label>
- <description>Date when minimum CO2 was reached this month</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMaxCo2" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max CO2</label>
- <description>Date when maximum CO2 was reached on current day</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMaxCo2ThisWeek" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max CO2 This Week</label>
- <description>Date when maximum CO2 was reached this week</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMaxCo2ThisMonth" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max CO2 This Month</label>
- <description>Date when maximum CO2 was reached this month</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMinTemp" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Temp</label>
- <description>Date when minimum temperature was reached on current day</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMinTempThisWeek" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Temp This Week</label>
- <description>Date when minimum temperature was reached this week</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMinTempThisMonth" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Temp This Month</label>
- <description>Date when minimum temperature was reached this month</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMaxTemp" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Temp</label>
- <description>Date when maximum temperature was reached on current day</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMaxTempThisWeek" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Temp This Week</label>
- <description>Date when maximum temperature was reached this week</description>
- <category>Time</category>
- <state readOnly="true"/>
- </channel-type>
-
- <channel-type id="dateMaxTempThisMonth" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Temp This Month</label>
- <description>Date when maximum temperature was reached this month</description>
- <category>Time</category>
+ <channel-type id="anticipating-heating">
+ <item-type>Switch</item-type>
+ <label>Anticipated Heating</label>
+ <description>Anticipates next scheduled setpoint.</description>
<state readOnly="true"/>
</channel-type>
- <channel-type id="dateMinHumidity" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Humidity</label>
- <description>Date when minimum humidity was reached on current day</description>
- <category>Time</category>
+ <channel-type id="heating-status">
+ <item-type>Contact</item-type>
+ <label>Heating Status</label>
+ <description>Is the furnace currently heating?</description>
<state readOnly="true"/>
</channel-type>
- <channel-type id="dateMinHumidityThisWeek" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Humidity This Week</label>
- <description>Date when minimum humidity was reached this week</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="camera-event">
+ <kind>trigger</kind>
+ <label>Camera Event</label>
+ <event>
+ <options>
+ <option value="ANIMAL"/>
+ <option value="HUMAN"/>
+ <option value="MOVEMENT"/>
+ <option value="VEHICLE"/>
+ </options>
+ </event>
</channel-type>
- <channel-type id="dateMinHumidityThisMonth" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Humidity This Month</label>
- <description>Date when minimum humidity was reached this month</description>
- <category>Time</category>
+ <channel-type id="battery-status">
+ <item-type>String</item-type>
+ <label>Battery Status</label>
+ <description>Description of the battery status.</description>
<state readOnly="true"/>
</channel-type>
- <channel-type id="dateMaxHumidity" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Humidity</label>
- <description>Date when maximum humidity was reached on current day</description>
- <category>Time</category>
+ <channel-type id="video-url">
+ <item-type>String</item-type>
+ <label>Video URL</label>
+ <description>URL of the event recording.</description>
<state readOnly="true"/>
</channel-type>
- <channel-type id="dateMaxHumidityThisWeek" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Humidity This Week</label>
- <description>Date when maximum humidity was reached this week</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="planning">
+ <item-type>String</item-type>
+ <label>Planning</label>
+ <description>Planning currently applied when following weekly schedule.</description>
+ <state readOnly="false" pattern="%s"/>
</channel-type>
- <channel-type id="dateMaxHumidityThisMonth" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Humidity This Month</label>
- <description>Date when maximum humidity was reached this month</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="person-count">
+ <item-type>Number</item-type>
+ <label>Person Count</label>
+ <description>Total number of persons that are at home.</description>
+ <tags>
+ <tag>Status</tag>
+ <tag>Presence</tag>
+ </tags>
+ <state readOnly="true" pattern="%d"/>
</channel-type>
- <channel-type id="dateMinNoise" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Noise</label>
- <description>Date when minimum noise was reached on current day</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="unknown-person-count">
+ <item-type>Number</item-type>
+ <label>Unknown Persons Count</label>
+ <description>Total number of unknown persons that are at home.</description>
+ <state readOnly="true" pattern="%d"/>
</channel-type>
- <channel-type id="dateMinNoiseThisWeek" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Noise This Week</label>
- <description>Date when minimum noise was reached this week</description>
- <category>Time</category>
+ <channel-type id="unknown-person-picture">
+ <item-type>Image</item-type>
+ <label>Unknown Person Snapshot</label>
+ <description>Snapshot of unknown person that is at home.</description>
<state readOnly="true"/>
</channel-type>
- <channel-type id="dateMinNoiseThisMonth" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Noise This Month</label>
- <description>Date when minimum noise was reached this month</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="rain-quantity">
+ <item-type>Number:Length</item-type>
+ <label>Rain Quantity</label>
+ <description>Quantity of water over the period.</description>
+ <category>Rain</category>
+ <state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
- <channel-type id="dateMaxNoise" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Noise</label>
- <description>Date when maximum noise was reached on current day</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="rain-intensity">
+ <item-type>Number:Speed</item-type>
+ <label>Rain Intensity</label>
+ <description>Current precipitation intensity.</description>
+ <category>Rain</category>
+ <state readOnly="true" pattern="%.1f mm/h"/>
</channel-type>
- <channel-type id="dateMaxNoiseThisWeek" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Noise This Week</label>
- <description>Date when maximum noise was reached this week</description>
+ <channel-type id="setpoint-duration">
+ <item-type>Number:Time</item-type>
+ <label>Setpoint Duration</label>
+ <description>Default duration of manual setpoint changes.</description>
<category>Time</category>
- <state readOnly="true"/>
+ <state pattern="%d %unit%" min="5" max="720" step="5"/>
</channel-type>
- <channel-type id="dateMaxNoiseThisMonth" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Noise This Month</label>
- <description>Date when maximum noise was reached this month</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="floodlight-mode">
+ <item-type>String</item-type>
+ <label>Floodlight</label>
+ <description>State of the floodlight (On/Off/Auto)</description>
+ <state pattern="%s">
+ <options>
+ <option value="ON">On</option>
+ <option value="OFF">Off</option>
+ <option value="AUTO">Auto</option>
+ </options>
+ </state>
</channel-type>
- <channel-type id="dateMinPressure" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Pressure</label>
- <description>Date when minimum pressure was reached on current day</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="trend" advanced="true">
+ <item-type>String</item-type>
+ <label>Trend</label>
+ <description>Evolution of the measure over time.</description>
+ <category>Line</category>
+ <state readOnly="true" pattern="%s">
+ <options>
+ <option value="UP">Up</option>
+ <option value="STABLE">Stable</option>
+ <option value="DOWN">Down</option>
+ </options>
+ </state>
</channel-type>
- <channel-type id="dateMinPressureThisWeek" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Pressure This Week</label>
- <description>Date when minimum pressure was reached this week</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="sd-card-status">
+ <item-type>String</item-type>
+ <label>SD Card State</label>
+ <description>State of the SD card</description>
+ <state readOnly="true" pattern="%s">
+ <options>
+ <option value="SD_CARD_MISSING">Missing SD Card</option>
+ <option value="SD_CARD_INSERTED">SD Card inserted</option>
+ <option value="SD_CARD_FORMATTED">SD Card formated</option>
+ <option value="SD_CARD_WORKING">Working SD Card</option>
+ <option value="SD_CARD_DEFECTIVE">Defective SD Card</option>
+ <option value="SD_CARD_INCOMPATIBLE_SPEED">Incompatible SD Card speed</option>
+ <option value="SD_CARD_INSUFFICIENT_SPACE">Insufficient SD Card space</option>
+ </options>
+ </state>
</channel-type>
- <channel-type id="dateMinPressureThisMonth" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Min Pressure This Month</label>
- <description>Date when minimum pressure was reached this month</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="alim-status">
+ <item-type>String</item-type>
+ <label>Alim State</label>
+ <description>State of the power connector</description>
+ <state readOnly="true" pattern="%s">
+ <options>
+ <option value="ALIM_INCORRECT_POWER">Incorrect power adapter</option>
+ <option value="ALIM_CORRECT_POWER">Correct power adapter</option>
+ </options>
+ </state>
</channel-type>
- <channel-type id="dateMaxPressure" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Pressure</label>
- <description>Date when maximum pressure was reached on current day</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="video-status">
+ <item-type>String</item-type>
+ <label>Video Status</label>
+ <description>Status of the video (recording, deleted or available).</description>
+ <state readOnly="true">
+ <options>
+ <option value="RECORDING">Recording</option>
+ <option value="DELETED">Deleted</option>
+ <option value="AVAILABLE">Available</option>
+ </options>
+ </state>
</channel-type>
- <channel-type id="dateMaxPressureThisWeek" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Pressure This Week</label>
- <description>Date when maximum pressure was reached this week</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="setpoint">
+ <item-type>Number:Temperature</item-type>
+ <label>Setpoint</label>
+ <description>Thermostat temperature setpoint.</description>
+ <category>Temperature</category>
+ <tags>
+ <tag>Setpoint</tag>
+ <tag>Temperature</tag>
+ </tags>
+ <state pattern="%.1f %unit%" readOnly="false" min="7" max="30" step="0.5"/>
</channel-type>
- <channel-type id="dateMaxPressureThisMonth" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Pressure This Month</label>
- <description>Date when maximum pressure was reached this month</description>
- <category>Time</category>
- <state readOnly="true"/>
+ <channel-type id="th-mode">
+ <item-type>String</item-type>
+ <label>Thermostat Mode</label>
+ <description>Chosen thermostat mode (home, frost guard, manual, max).</description>
+ <state readOnly="false">
+ <options>
+ <option value="HOME">Home</option>
+ <option value="FROST_GUARD">Frost Guard</option>
+ <option value="MANUAL">Manual</option>
+ <option value="MAX">Max</option>
+ </options>
+ </state>
</channel-type>
- <channel-type id="lastBilan" advanced="true">
- <item-type>DateTime</item-type>
- <label>Available Bilan</label>
- <description>Month of the last available thermostat bilan</description>
- <category>Time</category>
- <state readOnly="true" pattern="%1$td.%1$tm.%1$tY"/>
+ <channel-type id="energy-mode">
+ <item-type>String</item-type>
+ <label>House Mode</label>
+ <description>Chosen mode for the house (schedule, away, frost guard, manual).</description>
+ <state readOnly="false">
+ <options>
+ <option value="SCHEDULE">Following a weekly schedule</option>
+ <option value="AWAY">Applying the -away- temperature as defined by the user</option>
+ <option value="FROST_GUARD">Frost-guard</option>
+ <option value="MANUAL">Applying a manually set temperature setpoint</option>
+ </options>
+ </state>
</channel-type>
- <channel-type id="connectedBoiler" advanced="true">
- <item-type>Switch</item-type>
- <label>Plug Connected Boiler</label>
- <state readOnly="true"/>
+ <channel-type id="room-heating-percent">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Heating Power</label>
+ <description>Percentage of heating power.</description>
+ <category>Energy</category>
+ <state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
- <channel-type id="lastMessage" advanced="true">
+ <channel-type id="timestamp">
<item-type>DateTime</item-type>
- <label>Last Message</label>
- <description>Last Message emitted by the module</description>
+ <label>Timestamp</label>
+ <description>Moment when data was measured.</description>
<category>Time</category>
<state readOnly="true"/>
</channel-type>
- <channel-type id="setpointEndTime" advanced="true">
+ <channel-type id="timestamp-advanced" advanced="true">
<item-type>DateTime</item-type>
- <label>Setpoint End</label>
- <description>Thermostat goes back to schedule after that timestamp.</description>
- <category>Time</category>
- <state readOnly="true" pattern="%1$td.%1$tm.%1$tY %1$tH:%1$tM"/>
- </channel-type>
-
- <channel-type id="lastThermSeen" advanced="true">
- <item-type>DateTime</item-type>
- <label>Last Therm Seen</label>
- <description>Last Them Seen</description>
+ <label>Timestamp</label>
+ <description>Moment when data was measured.</description>
<category>Time</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="co2">
<item-type>Number:Dimensionless</item-type>
<label>CO2</label>
- <description>Air Quality</description>
- <category>Carbondioxide</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="minCo2" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Min Co2</label>
- <description>Minimum CO2 on current day</description>
- <category>Carbondioxide</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="minCo2ThisWeek" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Min Co2 This Week</label>
- <description>Minimum CO2 this week</description>
- <category>Carbondioxide</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="minCo2ThisMonth" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Min Co2 This Month</label>
- <description>Minimum CO2 this month</description>
- <category>Carbondioxide</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="maxCo2" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Max Co2</label>
- <description>Maximum CO2 on current day</description>
- <category>Carbondioxide</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="maxCo2ThisWeek" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Max Co2 This Week</label>
- <description>Maximum CO2 this week</description>
- <category>Carbondioxide</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="maxCo2ThisMonth" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Max Co2 This Month</label>
- <description>Maximum CO2 this month</description>
+ <description>Air Quality indicator.</description>
<category>Carbondioxide</category>
+ <tags>
+ <tag>Measurement</tag>
+ <tag>CO2</tag>
+ </tags>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="noise">
<item-type>Number:Dimensionless</item-type>
<label>Noise</label>
- <description>Current Noise Level</description>
- <category>Noise</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="minNoise" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Min Noise</label>
- <description>Minimum Noise on current day</description>
- <category>Noise</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="minNoiseThisWeek" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Min Noise This Week</label>
- <description>Minimum Noise this week</description>
- <category>Noise</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="minNoiseThisMonth" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Min Noise This Month</label>
- <description>Minimum Noise this month</description>
- <category>Noise</category>
+ <description>Current Noise Level.</description>
+ <category>SoundVolume</category>
+ <tags>
+ <tag>Measurement</tag>
+ <tag>Noise</tag>
+ </tags>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
- <channel-type id="maxNoise" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Max Noise</label>
- <description>Maximum Noise on current day</description>
- <category>Noise</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="maxNoiseThisWeek" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Max Noise This Week</label>
- <description>Maximum Noise this week</description>
- <category>Noise</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="maxNoiseThisMonth" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Max Noise This Month</label>
- <description>Maximum Noise this month</description>
- <category>Noise</category>
- <state readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="healthindex">
- <item-type>String</item-type>
+ <channel-type id="health-index">
+ <item-type>Number</item-type>
<label>Health Index</label>
- <description>Health Index (healthy, fine, fair, poor, unhealthy)</description>
- <state readOnly="true" pattern="%s">
+ <description>Health index (healthy, fine, fair, poor, unhealthy).</description>
+ <state readOnly="true" pattern="%d">
<options>
- <option value="healthy">healthy</option>
- <option value="fine">fine</option>
- <option value="fair">fair</option>
- <option value="poor">poor</option>
- <option value="unhealthy">unhealthy</option>
+ <option value="0">Healthy</option>
+ <option value="1">Fine</option>
+ <option value="2">Fair</option>
+ <option value="3">Poor</option>
+ <option value="4">Unhealthy</option>
</options>
</state>
</channel-type>
- <channel-type id="pressure">
- <item-type>Number:Pressure</item-type>
- <label>Pressure</label>
- <description>Current pressure</description>
- <category>Pressure</category>
- <state readOnly="true" pattern="%.3f %unit%"/>
- </channel-type>
-
- <channel-type id="minPressure" advanced="true">
- <item-type>Number:Pressure</item-type>
- <label>Min Pressure</label>
- <description>Minimum Pressure on current day</description>
- <category>Pressure</category>
- <state readOnly="true" pattern="%.3f %unit%"/>
- </channel-type>
-
- <channel-type id="minPressureThisWeek" advanced="true">
- <item-type>Number:Pressure</item-type>
- <label>Min Pressure This Week</label>
- <description>Minimum Pressure this week</description>
- <category>Pressure</category>
- <state readOnly="true" pattern="%.3f %unit%"/>
- </channel-type>
-
- <channel-type id="minPressureThisMonth" advanced="true">
- <item-type>Number:Pressure</item-type>
- <label>Min Pressure This Month</label>
- <description>Minimum Pressure this month</description>
- <category>Pressure</category>
- <state readOnly="true" pattern="%.3f %unit%"/>
- </channel-type>
-
- <channel-type id="maxPressure" advanced="true">
- <item-type>Number:Pressure</item-type>
- <label>Max Pressure</label>
- <description>Maximum Pressure on current day</description>
- <category>Pressure</category>
- <state readOnly="true" pattern="%.3f %unit%"/>
+ <channel-type id="humidex" advanced="true">
+ <item-type>Number</item-type>
+ <label>Humidex</label>
+ <description>Computed Humidex: felt temperature.</description>
+ <state readOnly="true" pattern="%.0f"/>
</channel-type>
- <channel-type id="maxPressureThisWeek" advanced="true">
- <item-type>Number:Pressure</item-type>
- <label>Max Pressure This Week</label>
- <description>Maximum Pressure this week</description>
- <category>Pressure</category>
- <state readOnly="true" pattern="%.3f %unit%"/>
+ <channel-type id="humidex-scale" advanced="true">
+ <item-type>Number</item-type>
+ <label>Humidex Appreciation</label>
+ <description>Appreciation of the Humidex.</description>
+ <state readOnly="true" pattern="%d">
+ <options>
+ <option value="0">Comfortable</option>
+ <option value="1">Some discomfort</option>
+ <option value="2">Great discomfort</option>
+ <option value="3">Dangerous</option>
+ <option value="4">Very dangerous</option>
+ </options>
+ </state>
</channel-type>
- <channel-type id="maxPressureThisMonth" advanced="true">
- <item-type>Number:Pressure</item-type>
- <label>Max Pressure This Month</label>
- <description>Maximum Pressure this month</description>
- <category>Pressure</category>
- <state readOnly="true" pattern="%.3f %unit%"/>
+ <channel-type id="event-type">
+ <item-type>String</item-type>
+ <label>Event Type</label>
+ <description>Description of the event.</description>
+ <state readOnly="true">
+ <options>
+ <option value="PERSON">Face detected</option>
+ <option value="PERSON_AWAY">Person has left home</option>
+ <option value="PERSON_HOME">Person is at home</option>
+ <option value="OUTDOOR">Motion detected by Presence</option>
+ <option value="MOVEMENT">Motion detected</option>
+ <option value="HUMAN">Human seen</option>
+ <option value="ANIMAL">Animal seen</option>
+ <option value="NEW_MODULE">New Module has been paired</option>
+ <option value="MODULE_CONNECT">Module is connected with the Indoor Camera</option>
+ <option value="MODULE_DISCONNECT">Module lost its connection with the Indoor Camera</option>
+ <option value="MODULE_LOW_BATTERY">Module's battery is low</option>
+ <option value="MODULE_END_UPDATE">Module's firmware update is over</option>
+ <option value="CONNECTION">Camera connected to Netatmo</option>
+ <option value="DISCONNECTION">Camera disconnected from Netatmo</option>
+ <option value="ON">Monitoring activated</option>
+ <option value="OFF">Monitoring stopped</option>
+ <option value="BOOT">Camera booting</option>
+ <option value="SD">SD card status changed</option>
+ <option value="ALIM">Power status changed</option>
+ </options>
+ </state>
</channel-type>
- <channel-type id="pressureTrend" advanced="true">
+ <channel-type id="event-subtype">
<item-type>String</item-type>
- <label>Pressure Trend</label>
- <description>Pressure evolution trend for last 12h (up, down, stable)</description>
- <category>Pressure</category>
- <state readOnly="true" pattern="%s">
+ <label>Event Sub Type</label>
+ <description>Details of the event.</description>
+ <state readOnly="true">
<options>
- <option value="up">up</option>
- <option value="stable">stable</option>
- <option value="down">down</option>
+ <option value="SD_CARD_MISSING">Missing SD Card</option>
+ <option value="SD_CARD_INSERTED">SD Card inserted</option>
+ <option value="SD_CARD_FORMATTED">SD Card formated</option>
+ <option value="SD_CARD_WORKING">Working SD Card</option>
+ <option value="SD_CARD_DEFECTIVE">Defective SD Card</option>
+ <option value="SD_CARD_INCOMPATIBLE_SPEED">Incompatible SD Card speed</option>
+ <option value="SD_CARD_INSUFFICIENT_SPACE">Insufficient SD Card space</option>
+ <option value="ALIM_INCORRECT_POWER">Incorrect power adapter</option>
+ <option value="ALIM_CORRECT_POWER">Correct power adapter</option>
+ <option value="PERSON_ARRIVAL">Person arrived</option>
+ <option value="PERSON_DEPARTURE">Person has left</option>
+ <option value="PERSON_SEEN">Person has been seen</option>
+ <option value="MOVEMENT_HUMAN">Human seen</option>
+ <option value="MOVEMENT_VEHICLE">Car seen</option>
+ <option value="MOVEMENT_ANIMAL">Animal seen</option>
</options>
</state>
</channel-type>
- <channel-type id="planning" advanced="false">
+ <channel-type id="home-event">
+ <kind>trigger</kind>
+ <label>Home Event</label>
+ <event>
+ <options>
+ <option value="PERSON"/>
+ <option value="PERSON_AWAY"/>
+ <option value="PERSON_HOME"/>
+ <option value="OUTDOOR"/>
+ <option value="MOVEMENT"/>
+ <option value="HUMAN"/>
+ <option value="ANIMAL"/>
+ <option value="NEW_MODULE"/>
+ <option value="MODULE_CONNECT"/>
+ <option value="MODULE_DISCONNECT"/>
+ <option value="MODULE_LOW_BATTERY"/>
+ <option value="MODULE_END_UPDATE"/>
+ <option value="CONNECTION"/>
+ <option value="DISCONNECTION"/>
+ <option value="ON"/>
+ <option value="OFF"/>
+ <option value="BOOT"/>
+ <option value="SD"/>
+ <option value="ALIM"/>
+ </options>
+ </event>
+ </channel-type>
+
+ <channel-type id="message">
<item-type>String</item-type>
- <label>Planning</label>
- <description>Heat planning currently used</description>
- <state pattern="%s"/>
+ <label>Message</label>
+ <description>Message sent by Netatmo corresponding to given event.</description>
+ <state readOnly="true"/>
</channel-type>
- <channel-type id="absolutePressure" advanced="true">
- <item-type>Number:Pressure</item-type>
- <label>Abs Pressure</label>
- <description>Absolute pressure</description>
- <category>Pressure</category>
- <state readOnly="true" pattern="%.3f %unit%"/>
+ <channel-type id="event-picture">
+ <item-type>Image</item-type>
+ <label>Event Snapshot</label>
+ <description>Capture image of the event.</description>
+ <state readOnly="true"/>
</channel-type>
- <channel-type id="humidity">
- <item-type>Number:Dimensionless</item-type>
- <label>Humidity</label>
- <description>Current humidity</description>
- <category>Humidity</category>
- <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+ <channel-type id="event-picture-url" advanced="true">
+ <item-type>String</item-type>
+ <label>Event Snapshot URL</label>
+ <description>Url of the event snapshot.</description>
+ <state readOnly="true"></state>
</channel-type>
- <channel-type id="minHumidity" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Min Humidity</label>
- <description>Minimum Humidity on current day</description>
- <category>Humidity</category>
- <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+ <channel-type id="person-id">
+ <item-type>String</item-type>
+ <label>Person ID</label>
+ <state readOnly="true"/>
</channel-type>
- <channel-type id="minHumidityThisWeek" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Min Humidity This Week</label>
- <description>Minimum Humidity this week</description>
- <category>Humidity</category>
- <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+ <channel-type id="camera-id">
+ <item-type>String</item-type>
+ <label>Camera ID</label>
+ <description>ID of the camera that triggered the event.</description>
+ <state readOnly="true"/>
</channel-type>
- <channel-type id="minHumidityThisMonth" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Min Humidity This Month</label>
- <description>Minimum Humidity this month</description>
- <category>Humidity</category>
- <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+ <channel-type id="avatar-picture-url">
+ <item-type>String</item-type>
+ <label>Avatar Picture URL</label>
+ <description>URL for the avatar of this person.</description>
+ <state readOnly="true"/>
</channel-type>
- <channel-type id="maxHumidity" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Max Humidity</label>
- <description>Maximum Humidity on current day</description>
- <category>Humidity</category>
- <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+ <channel-type id="avatar-picture">
+ <item-type>Image</item-type>
+ <label>Avatar Picture</label>
+ <description>Avatar of this person.</description>
+ <state readOnly="true"/>
</channel-type>
- <channel-type id="maxHumidityThisWeek" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Max Humidity This Week</label>
- <description>Minimum Humidity this week</description>
- <category>Humidity</category>
- <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+ <channel-type id="at-home">
+ <item-type>Switch</item-type>
+ <label>At Home</label>
+ <description>Indicates if this person is known to be at home or not.</description>
</channel-type>
- <channel-type id="maxHumidityThisMonth" advanced="true">
- <item-type>Number:Dimensionless</item-type>
- <label>Max Humidity This Month</label>
- <description>Maximum Humidity this month</description>
- <category>Humidity</category>
- <state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
+ <channel-type id="rssi" advanced="true">
+ <item-type>Number:Power</item-type>
+ <label>Signal</label>
+ <description>Signal strength indicator.</description>
+ <category>QualityOfService</category>
+ <state readOnly="true" pattern="%d %unit%"></state>
</channel-type>
- <channel-type id="humidex">
- <item-type>Number</item-type>
- <label>Humidex</label>
- <description>Computed Humidex index</description>
- <category>Temperature</category>
- <state readOnly="true" pattern="%.0f"/>
+ <channel-type id="absolute-pressure" advanced="true">
+ <item-type>Number:Pressure</item-type>
+ <label>Absolute Pressure</label>
+ <description>Pressure measured relative to a full vacuum.</description>
+ <category>Pressure</category>
+ <tags>
+ <tag>Measurement</tag>
+ <tag>Pressure</tag>
+ </tags>
+ <state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
- <channel-type id="heatIndex">
+ <channel-type id="heat-index">
<item-type>Number:Temperature</item-type>
<label>Heat Index</label>
- <description>Computed Heat Index</description>
+ <description>Apparent computed temperature (based on temperature and humidity).</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
- <channel-type id="dewPoint" advanced="true">
+ <channel-type id="dewpoint" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Dewpoint</label>
- <description>Computed Dewpoint Temperature</description>
+ <description>Temperature to which air must be cooled to become saturated with water vapor.</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
- <channel-type id="dewPointDepression" advanced="true">
+ <channel-type id="dewpoint-depression" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Dewpoint Depression</label>
- <description>Computed Dewpoint Depression</description>
+ <description>Difference between the temperature and the dewpoint.</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
- <channel-type id="rain">
- <item-type>Number:Length</item-type>
- <label>Rain</label>
- <description>Quantity of water</description>
- <category>Rain</category>
- <state readOnly="true" pattern="%.2f %unit%"/>
- </channel-type>
-
- <channel-type id="rain1" advanced="true">
- <item-type>Number:Length</item-type>
- <label>Rain 1h</label>
- <description>Quantity of water on last hour</description>
- <category>Rain</category>
- <state readOnly="true" pattern="%.2f %unit%"/>
- </channel-type>
-
- <channel-type id="rain24" advanced="true">
- <item-type>Number:Length</item-type>
- <label>Rain 24h</label>
- <description>Quantity of water on last day</description>
- <category>Rain</category>
- <state readOnly="true" pattern="%.2f %unit%"/>
- </channel-type>
-
- <channel-type id="rainThisWeek" advanced="true">
- <item-type>Number:Length</item-type>
- <label>Rain This Week</label>
- <description>Quantity of water this week</description>
- <category>Rain</category>
- <state readOnly="true" pattern="%.2f %unit%"/>
+ <channel-type id="max-temp" advanced="true">
+ <item-type>Number:Temperature</item-type>
+ <label>Max Temp</label>
+ <description>Maximum Temperature on current day.</description>
+ <category>Temperature</category>
+ <state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
- <channel-type id="rainThisMonth" advanced="true">
- <item-type>Number:Length</item-type>
- <label>Rain This Month</label>
- <description>Quantity of water this month</description>
- <category>Rain</category>
- <state readOnly="true" pattern="%.2f %unit%"/>
+ <channel-type id="min-temp" advanced="true">
+ <item-type>Number:Temperature</item-type>
+ <label>Min Temp</label>
+ <description>Minimum Temperature on current day</description>
+ <category>Temperature</category>
+ <state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
- <channel-type id="WindAngle">
+ <channel-type id="gust-angle">
<item-type>Number:Angle</item-type>
- <label>Wind Angle</label>
- <description>Current 5 minutes average wind direction</description>
+ <label>Gust Angle</label>
+ <description>Direction of the last 5 minutes highest gust wind</description>
<category>Wind</category>
<state min="0" max="360" step="1" readOnly="true" pattern="%d %unit%"/>
</channel-type>
- <channel-type id="WindStrength">
+ <channel-type id="gust-strength">
<item-type>Number:Speed</item-type>
- <label>Wind Strength</label>
- <description>Current 5 minutes average wind speed</description>
+ <label>Gust Strength</label>
+ <description>Speed of the last 5 minutes highest gust wind</description>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
- <channel-type id="MaxWindStrength" advanced="true">
+ <channel-type id="max-wind-strength" advanced="true">
<item-type>Number:Speed</item-type>
<label>Max Wind Strength</label>
<description>Maximum wind strength recorded</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
- <channel-type id="DateMaxWindStrength" advanced="true">
- <item-type>DateTime</item-type>
- <label>Date Max Wind Strength</label>
- <description>Timestamp when MaxWindStrength was recorded.</description>
- <category>Time</category>
- <state readOnly="true" pattern="%1$td.%1$tm.%1$tY %1$tH:%1$tM"/>
- </channel-type>
-
- <channel-type id="GustAngle">
- <item-type>Number:Angle</item-type>
- <label>Gust Angle</label>
- <description>Direction of the last 5 minutes highest gust wind</description>
- <category>Wind</category>
- <state min="0" max="360" step="1" readOnly="true" pattern="%d %unit%"/>
- </channel-type>
-
- <channel-type id="GustStrength">
- <item-type>Number:Speed</item-type>
- <label>Gust Strength</label>
- <description>Speed of the last 5 minutes highest gust wind</description>
- <category>Wind</category>
- <state readOnly="true" pattern="%.1f %unit%"/>
+ <channel-type id="location">
+ <item-type>Location</item-type>
+ <label>Location</label>
+ <description>Location of the device</description>
+ <state readOnly="true" pattern="%2$s°N,%3$s°W, %1$s m"/>
</channel-type>
- <channel-type id="homeEvent">
- <kind>trigger</kind>
- <label>Home Event</label>
- <description>Home event</description>
- <event>
- <options>
- <option value="PERSON">person</option>
- <option value="PERSON_AWAY">person_away</option>
- <option value="MOVEMENT">movement</option>
- <option value="CONNECTION">connection</option>
- <option value="DISCONNECTION">disconnection</option>
- <option value="ON">on</option>
- <option value="OFF">off</option>
- <option value="BOOT">boot</option>
- <option value="SD">sd</option>
- <option value="ALIM">alim</option>
- <option value="NEW_MODULE">new_module</option>
- <option value="MODULE_CONNECT">module_connect</option>
- <option value="MODULE_DISCONNECT">module_disconnect</option>
- <option value="MODULE_LOW_BATTERY">module_low_battery</option>
- <option value="MODULE_END_UPDATE">module_end_update</option>
- <option value="TAG_BIG_MOVE">tag_big_move</option>
- <option value="TAG_SMALL_MOVE">tag_small_move</option>
- <option value="TAG_UNINSTALLED">tag_uninstalled</option>
- <option value="TAG_OPEN">tag_open</option>
- </options>
- </event>
+ <channel-type id="live-stream-url">
+ <item-type>String</item-type>
+ <label>Live Stream URL</label>
+ <description>URL of the live stream for this camera.</description>
+ <state readOnly="true"></state>
+ <config-description>
+ <parameter name="quality" type="text">
+ <label>Quality Level</label>
+ <description>Defines quality level of the feed (the higher the more bandwidth)</description>
+ <options>
+ <option value="low">Low Quality</option>
+ <option value="poor">Poor Quality</option>
+ <option value="high">High Quality</option>
+ </options>
+ <default>poor</default>
+ </parameter>
+ </config-description>
+ </channel-type>
+
+ <channel-type id="live-picture">
+ <item-type>Image</item-type>
+ <label>Live Snapshot</label>
+ <description>Camera Live Snapshot.</description>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="live-picture-url" advanced="true">
+ <item-type>String</item-type>
+ <label>Live Snapshot URL</label>
+ <description>URL of the live snapshot for this camera (need scope access_camera).</description>
+ <state readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="netatmo"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="signal">
+ <label>Signal</label>
+ <channels>
+ <channel id="value" typeId="rssi"/>
+ <channel id="strength" typeId="system.signal-strength"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="battery">
+ <label>Battery</label>
+ <channels>
+ <channel id="value" typeId="system.battery-level"/>
+ <channel id="low-battery" typeId="system.low-battery"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="location">
+ <label>Location</label>
+ <channels>
+ <channel id="value" typeId="location"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="battery-extended">
+ <label>Battery</label>
+ <channels>
+ <channel id="value" typeId="system.battery-level"/>
+ <channel id="status" typeId="battery-status"/>
+ <channel id="low-battery" typeId="system.low-battery"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="timestamp">
+ <label>Timestamp</label>
+ <channels>
+ <channel id="last-seen" typeId="timestamp">
+ <label>Last Seen</label>
+ <description>Last time the module reported its presence.</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="timestamp-extended">
+ <label>Timestamp</label>
+ <channels>
+ <channel id="measures" typeId="timestamp">
+ <label>Measures Timestamp</label>
+ <description>Moment of the last measures update.</description>
+ </channel>
+ <channel id="last-seen" typeId="timestamp-advanced">
+ <label>Last Seen</label>
+ <description>Last time the module reported its presence.</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="netatmo"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="plug">
+ <label>Thermostat Plug</label>
+ <channels>
+ <channel id="boiler-status" typeId="boiler-status"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="room-temperature">
+ <label>Room Temperature</label>
+ <channels>
+ <channel id="value" typeId="system.indoor-temperature"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="room-properties">
+ <label>Room Status</label>
+ <channels>
+ <channel id="window-open" typeId="window-open"/>
+ <channel id="anticipating" typeId="anticipating-heating"/>
+ <channel id="heating-power-request" typeId="room-heating-percent"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="setpoint">
+ <label>Setpoint</label>
+ <channels>
+ <channel id="value" typeId="setpoint"/>
+ <channel id="mode" typeId="th-mode"/>
+ <channel id="start" typeId="timestamp">
+ <label>Setpoint Start</label>
+ <description>Start time of the currently applied setpoint.</description>
+ </channel>
+ <channel id="end" typeId="timestamp">
+ <label>Setpoint End</label>
+ <description>End time of the currently applied setpoint.</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="th-properties">
+ <label>Thermostat</label>
+ <channels>
+ <channel id="relay-status" typeId="heating-status"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="energy">
+ <label>Home Energy</label>
+ <channels>
+ <channel id="setpoint-duration" typeId="setpoint-duration"/>
+ <channel id="planning" typeId="planning"/>
+ <channel id="mode" typeId="energy-mode"/>
+ <channel id="end" typeId="timestamp">
+ <label>Mode End</label>
+ <description>End time of the currently applied setpoint.</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
- xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
- <thing-type id="NHC">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Healthy Home Coach</label>
- <description>This represents the healthy home coach capable of reporting health
- index,temperature,humidity,pressure,air quality and sound level</description>
- <channels>
- <channel id="HealthIndex" typeId="healthindex"/>
- <channel id="Co2" typeId="co2"/>
- <channel id="Temperature" typeId="temperature"/>
- <channel id="TempTrend" typeId="temperatureTrend"/>
- <channel id="Noise" typeId="noise"/>
- <channel id="Pressure" typeId="pressure"/>
- <channel id="PressTrend" typeId="pressureTrend"/>
- <channel id="AbsolutePressure" typeId="absolutePressure"/>
- <channel id="TimeStamp" typeId="timeUtc"/>
- <channel id="Humidity" typeId="humidity"/>
- <channel id="MinTemp" typeId="minTemp"/>
- <channel id="MaxTemp" typeId="maxTemp"/>
- <channel id="DateMinTemp" typeId="dateMinTemp"/>
- <channel id="DateMaxTemp" typeId="dateMaxTemp"/>
- <!-- Common to all devices -->
- <channel id="LastStatusStore" typeId="lastStatusStore"/>
- <channel id="WifiStatus" typeId="system.signal-strength"/>
- <channel id="Location" typeId="location"/>
- </channels>
-
- <properties>
- <property name="signalLevels">99,84,69,54</property>
- <property name="refreshPeriod">auto</property>
- </properties>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:station"/>
- </thing-type>
-</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="netatmo"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="security">
+ <label>Home Security</label>
+ <channels>
+ <channel id="person-count" typeId="person-count"/>
+ <channel id="unknown-person-count" typeId="unknown-person-count"/>
+ <channel id="unknown-person-picture" typeId="unknown-person-picture"/>
+ <channel id="home-event" typeId="home-event"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="status">
+ <label>Camera Status</label>
+ <channels>
+ <channel id="monitoring" typeId="monitoring-status"/>
+ <channel id="sd-card" typeId="sd-card-status"/>
+ <channel id="alim" typeId="alim-status"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="live">
+ <label>Live Monitoring</label>
+ <channels>
+ <channel id="picture" typeId="live-picture"/>
+ <channel id="local-picture-url" typeId="live-picture-url">
+ <label>Live Snapshot Local URL</label>
+ <description>Local URL of the live snapshot for this camera.</description>
+ </channel>
+ <channel id="vpn-picture-url" typeId="live-picture-url">
+ <label>Live Snapshot VPN URL</label>
+ <description>URL of the live snapshot for this camera through Netatmo VPN.</description>
+ </channel>
+ <channel id="local-stream-url" typeId="live-stream-url">
+ <label>Live Stream Local URL</label>
+ <description>Local URL of the live stream for this camera.</description>
+ </channel>
+ <channel id="vpn-stream-url" typeId="live-stream-url">
+ <label>Live Stream VPN URL</label>
+ <description>URL of the live stream for this camera through Netatmo VPN.</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="last-event">
+ <label>Last Event</label>
+ <channels>
+ <channel id="type" typeId="event-type"/>
+ <channel id="subtype" typeId="event-subtype"/>
+ <channel id="person-id" typeId="person-id"/>
+ <channel id="video-status" typeId="video-status"/>
+ <channel id="message" typeId="message"/>
+ <channel id="time" typeId="timestamp">
+ <label>Event Timestamp</label>
+ <description>Moment when event occurred.</description>
+ </channel>
+ <channel id="snapshot" typeId="event-picture"/>
+ <channel id="snapshot-url" typeId="event-picture-url"/>
+ <channel id="local-video-url" typeId="video-url">
+ <label>Video Local URL</label>
+ <description>Local URL of the event recording.</description>
+ </channel>
+ <channel id="vpn-video-url" typeId="video-url">
+ <label>Video VPN URL</label>
+ <description>URL of the event recording through Netatmo VPN.</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="person-event">
+ <label>Last Event</label>
+ <channels>
+ <channel id="subtype" typeId="event-subtype"/>
+ <channel id="message" typeId="message">
+ <description>Last event message from this person.</description>
+ </channel>
+ <channel id="time" typeId="timestamp">
+ <label>Person Timestamp</label>
+ <description>Moment of the last event for this person.</description>
+ </channel>
+ <channel id="snapshot" typeId="event-picture">
+ <description>Picture of the last event for this person.</description>
+ </channel>
+ <channel id="snapshot-url" typeId="event-picture-url">
+ <description>URL for the picture of the last event for this person.</description>
+ </channel>
+ <channel id="camera-id" typeId="camera-id"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="person">
+ <label>Person</label>
+ <channels>
+ <channel id="avatar-url" typeId="avatar-picture-url"/>
+ <channel id="avatar" typeId="avatar-picture"/>
+ <channel id="at-home" typeId="at-home"/>
+ <channel id="last-seen" typeId="timestamp">
+ <label>Last Seen</label>
+ <description>Moment when this person was last seen.</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="presence">
+ <label>Presence Camera</label>
+ <channels>
+ <channel id="floodlight" typeId="floodlight-mode"/>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
- xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
- <thing-type id="NAMain">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Main Indoor Station</label>
- <description>This represents the main indoor module capable of reporting temperature,humidity,pressure,air quality and
- sound level</description>
-
- <channels>
- <channel id="Co2" typeId="co2"/>
- <channel id="MinCo2" typeId="minCo2"/>
- <channel id="MinCo2ThisWeek" typeId="minCo2ThisWeek"/>
- <channel id="MinCo2ThisMonth" typeId="minCo2ThisMonth"/>
- <channel id="MaxCo2" typeId="maxCo2"/>
- <channel id="MaxCo2ThisWeek" typeId="maxCo2ThisWeek"/>
- <channel id="MaxCo2ThisMonth" typeId="maxCo2ThisMonth"/>
- <channel id="DateMinCo2" typeId="dateMinCo2"/>
- <channel id="DateMinCo2ThisWeek" typeId="dateMinCo2ThisWeek"/>
- <channel id="DateMinCo2ThisMonth" typeId="dateMinCo2ThisMonth"/>
- <channel id="DateMaxCo2" typeId="dateMaxCo2"/>
- <channel id="DateMaxCo2ThisWeek" typeId="dateMaxCo2ThisWeek"/>
- <channel id="DateMaxCo2ThisMonth" typeId="dateMaxCo2ThisMonth"/>
- <channel id="Temperature" typeId="temperature"/>
- <channel id="TempTrend" typeId="temperatureTrend"/>
- <channel id="Noise" typeId="noise"/>
- <channel id="MinNoise" typeId="minNoise"/>
- <channel id="MinNoiseThisWeek" typeId="minNoiseThisWeek"/>
- <channel id="MinNoiseThisMonth" typeId="minNoiseThisMonth"/>
- <channel id="MaxNoise" typeId="maxNoise"/>
- <channel id="MaxNoiseThisWeek" typeId="maxNoiseThisWeek"/>
- <channel id="MaxNoiseThisMonth" typeId="maxNoiseThisMonth"/>
- <channel id="DateMinNoise" typeId="dateMinNoise"/>
- <channel id="DateMinNoiseThisWeek" typeId="dateMinNoiseThisWeek"/>
- <channel id="DateMinNoiseThisMonth" typeId="dateMinNoiseThisMonth"/>
- <channel id="DateMaxNoise" typeId="dateMaxNoise"/>
- <channel id="DateMaxNoiseThisWeek" typeId="dateMaxNoiseThisWeek"/>
- <channel id="DateMaxNoiseThisMonth" typeId="dateMaxNoiseThisMonth"/>
- <channel id="Pressure" typeId="pressure"/>
- <channel id="MinPressure" typeId="minPressure"/>
- <channel id="MinPressureThisWeek" typeId="minPressureThisWeek"/>
- <channel id="MinPressureThisMonth" typeId="minPressureThisMonth"/>
- <channel id="MaxPressure" typeId="maxPressure"/>
- <channel id="MaxPressureThisWeek" typeId="maxPressureThisWeek"/>
- <channel id="MaxPressureThisMonth" typeId="maxPressureThisMonth"/>
- <channel id="DateMinPressure" typeId="dateMinPressure"/>
- <channel id="DateMinPressureThisWeek" typeId="dateMinPressureThisWeek"/>
- <channel id="DateMinPressureThisMonth" typeId="dateMinPressureThisMonth"/>
- <channel id="DateMaxPressure" typeId="dateMaxPressure"/>
- <channel id="DateMaxPressureThisWeek" typeId="dateMaxPressureThisWeek"/>
- <channel id="DateMaxPressureThisMonth" typeId="dateMaxPressureThisMonth"/>
- <channel id="PressTrend" typeId="pressureTrend"/>
- <channel id="AbsolutePressure" typeId="absolutePressure"/>
- <channel id="Humidity" typeId="humidity"/>
- <channel id="MinHumidity" typeId="minHumidity"/>
- <channel id="MinHumidityThisWeek" typeId="minHumidityThisWeek"/>
- <channel id="MinHumidityThisMonth" typeId="minHumidityThisMonth"/>
- <channel id="MaxHumidity" typeId="maxHumidity"/>
- <channel id="MaxHumidityThisWeek" typeId="maxHumidityThisWeek"/>
- <channel id="MaxHumidityThisMonth" typeId="maxHumidityThisMonth"/>
- <channel id="DateMinHumidity" typeId="dateMinHumidity"/>
- <channel id="DateMinHumidityThisWeek" typeId="dateMinHumidityThisWeek"/>
- <channel id="DateMinHumidityThisMonth" typeId="dateMinHumidityThisMonth"/>
- <channel id="DateMaxHumidity" typeId="dateMaxHumidity"/>
- <channel id="DateMaxHumidityThisWeek" typeId="dateMaxHumidityThisWeek"/>
- <channel id="DateMaxHumidityThisMonth" typeId="dateMaxHumidityThisMonth"/>
- <channel id="Humidex" typeId="humidex"/>
- <channel id="HeatIndex" typeId="heatIndex"/>
- <channel id="Dewpoint" typeId="dewPoint"/>
- <channel id="DewpointDepression" typeId="dewPointDepression"/>
- <channel id="MinTemp" typeId="minTemp"/>
- <channel id="MinTempThisWeek" typeId="minTempThisWeek"/>
- <channel id="MinTempThisMonth" typeId="minTempThisMonth"/>
- <channel id="MaxTemp" typeId="maxTemp"/>
- <channel id="MaxTempThisWeek" typeId="maxTempThisWeek"/>
- <channel id="MaxTempThisMonth" typeId="maxTempThisMonth"/>
- <channel id="DateMinTemp" typeId="dateMinTemp"/>
- <channel id="DateMinTempThisWeek" typeId="dateMinTempThisWeek"/>
- <channel id="DateMinTempThisMonth" typeId="dateMinTempThisMonth"/>
- <channel id="DateMaxTemp" typeId="dateMaxTemp"/>
- <channel id="DateMaxTempThisWeek" typeId="dateMaxTempThisWeek"/>
- <channel id="DateMaxTempThisMonth" typeId="dateMaxTempThisMonth"/>
- <!-- Common to all devices -->
- <channel id="TimeStamp" typeId="timeUtc"/>
- <channel id="LastStatusStore" typeId="lastStatusStore"/>
- <channel id="WifiStatus" typeId="system.signal-strength"/>
- <channel id="Location" typeId="location"/>
- </channels>
-
- <properties>
- <property name="signalLevels">99,84,69,54</property>
- <property name="refreshPeriod">auto</property>
- </properties>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:station"/>
- </thing-type>
-
- <thing-type id="NAModule1">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Outdoor Module</label>
- <description>This represents the outdoor module capable of reporting temperature and humidity</description>
-
- <channels>
- <channel id="Temperature" typeId="temperature"/>
- <channel id="TempTrend" typeId="temperatureTrend"/>
- <channel id="Humidity" typeId="humidity"/>
- <channel id="MinHumidity" typeId="minHumidity"/>
- <channel id="MinHumidityThisWeek" typeId="minHumidityThisWeek"/>
- <channel id="MinHumidityThisMonth" typeId="minHumidityThisMonth"/>
- <channel id="MaxHumidity" typeId="maxHumidity"/>
- <channel id="MaxHumidityThisWeek" typeId="maxHumidityThisWeek"/>
- <channel id="MaxHumidityThisMonth" typeId="maxHumidityThisMonth"/>
- <channel id="DateMinHumidity" typeId="dateMinHumidity"/>
- <channel id="DateMinHumidityThisWeek" typeId="dateMinHumidityThisWeek"/>
- <channel id="DateMinHumidityThisMonth" typeId="dateMinHumidityThisMonth"/>
- <channel id="DateMaxHumidity" typeId="dateMaxHumidity"/>
- <channel id="DateMaxHumidityThisWeek" typeId="dateMaxHumidityThisWeek"/>
- <channel id="DateMaxHumidityThisMonth" typeId="dateMaxHumidityThisMonth"/>
- <channel id="Humidex" typeId="humidex"/>
- <channel id="HeatIndex" typeId="heatIndex"/>
- <channel id="Dewpoint" typeId="dewPoint"/>
- <channel id="DewpointDepression" typeId="dewPointDepression"/>
- <channel id="MinTemp" typeId="minTemp"/>
- <channel id="MinTempThisWeek" typeId="minTempThisWeek"/>
- <channel id="MinTempThisMonth" typeId="minTempThisMonth"/>
- <channel id="MaxTemp" typeId="maxTemp"/>
- <channel id="MaxTempThisWeek" typeId="maxTempThisWeek"/>
- <channel id="MaxTempThisMonth" typeId="maxTempThisMonth"/>
- <channel id="DateMinTemp" typeId="dateMinTemp"/>
- <channel id="DateMinTempThisWeek" typeId="dateMinTempThisWeek"/>
- <channel id="DateMinTempThisMonth" typeId="dateMinTempThisMonth"/>
- <channel id="DateMaxTemp" typeId="dateMaxTemp"/>
- <channel id="DateMaxTempThisWeek" typeId="dateMaxTempThisWeek"/>
- <channel id="DateMaxTempThisMonth" typeId="dateMaxTempThisMonth"/>
- <!-- Common to all modules -->
- <channel id="TimeStamp" typeId="timeUtc"/>
- <channel id="LastMessage" typeId="lastMessage"/>
- <channel id="LowBattery" typeId="system.low-battery"/>
- <channel id="BatteryVP" typeId="system.battery-level"/>
- <channel id="RfStatus" typeId="system.signal-strength"/>
- </channels>
-
- <properties>
- <property name="signalLevels">90,80,70,60</property>
- <property name="batteryLevels">3600,4500,6000</property>
- </properties>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:module"/>
- </thing-type>
-
- <thing-type id="NAModule2">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Wind Gauge Module</label>
- <description>This represents the wind module capable of reporting wind angle and strength</description>
-
- <channels>
- <channel id="WindAngle" typeId="WindAngle"/>
- <channel id="WindStrength" typeId="WindStrength"/>
- <channel id="MaxWindStrength" typeId="MaxWindStrength"/>
- <channel id="DateMaxWindStrength" typeId="DateMaxWindStrength"/>
- <channel id="GustAngle" typeId="GustAngle"/>
- <channel id="GustStrength" typeId="GustStrength"/>
- <!-- Common to all modules -->
- <channel id="TimeStamp" typeId="timeUtc"/>
- <channel id="LastMessage" typeId="lastMessage"/>
- <channel id="LowBattery" typeId="system.low-battery"/>
- <channel id="BatteryVP" typeId="system.battery-level"/>
- <channel id="RfStatus" typeId="system.signal-strength"/>
- </channels>
-
- <properties>
- <property name="signalLevels">90,80,70,60</property>
- <property name="batteryLevels">3950,4770,6000</property>
- </properties>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:module"/>
- </thing-type>
-
- <thing-type id="NAModule3">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Rain Gauge</label>
- <description>This represents the Rain Gauge capable of measuring precipitation</description>
-
- <channels>
- <channel id="Rain" typeId="rain"/>
- <channel id="SumRain1" typeId="rain1"/>
- <channel id="SumRain24" typeId="rain24"/>
- <channel id="SumRainThisWeek" typeId="rainThisWeek"/>
- <channel id="SumRainThisMonth" typeId="rainThisMonth"/>
- <!-- Common to all modules -->
- <channel id="TimeStamp" typeId="timeUtc"/>
- <channel id="LastMessage" typeId="lastMessage"/>
- <channel id="LowBattery" typeId="system.low-battery"/>
- <channel id="BatteryVP" typeId="system.battery-level"/>
- <channel id="RfStatus" typeId="system.signal-strength"/>
- </channels>
-
- <properties>
- <property name="signalLevels">90,80,70,60</property>
- <property name="batteryLevels">3600,4500,6000</property>
- </properties>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:module"/>
- </thing-type>
-
- <thing-type id="NAModule4">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Additional Module</label>
- <description>This represents an additional indoor module capable of reporting temperature, humidity and CO2 level</description>
-
- <channels>
- <channel id="Co2" typeId="co2"/>
- <channel id="MinCo2" typeId="minCo2"/>
- <channel id="MinCo2ThisWeek" typeId="minCo2ThisWeek"/>
- <channel id="MinCo2ThisMonth" typeId="minCo2ThisMonth"/>
- <channel id="MaxCo2" typeId="maxCo2"/>
- <channel id="MaxCo2ThisWeek" typeId="maxCo2ThisWeek"/>
- <channel id="MaxCo2ThisMonth" typeId="maxCo2ThisMonth"/>
- <channel id="DateMinCo2" typeId="dateMinCo2"/>
- <channel id="DateMinCo2ThisWeek" typeId="dateMinCo2ThisWeek"/>
- <channel id="DateMinCo2ThisMonth" typeId="dateMinCo2ThisMonth"/>
- <channel id="DateMaxCo2" typeId="dateMaxCo2"/>
- <channel id="DateMaxCo2ThisWeek" typeId="dateMaxCo2ThisWeek"/>
- <channel id="DateMaxCo2ThisMonth" typeId="dateMaxCo2ThisMonth"/>
- <channel id="Temperature" typeId="temperature"/>
- <channel id="TempTrend" typeId="temperatureTrend"/>
- <channel id="Humidity" typeId="humidity"/>
- <channel id="MinHumidity" typeId="minHumidity"/>
- <channel id="MinHumidityThisWeek" typeId="minHumidityThisWeek"/>
- <channel id="MinHumidityThisMonth" typeId="minHumidityThisMonth"/>
- <channel id="MaxHumidity" typeId="maxHumidity"/>
- <channel id="MaxHumidityThisWeek" typeId="maxHumidityThisWeek"/>
- <channel id="MaxHumidityThisMonth" typeId="maxHumidityThisMonth"/>
- <channel id="DateMinHumidity" typeId="dateMinHumidity"/>
- <channel id="DateMinHumidityThisWeek" typeId="dateMinHumidityThisWeek"/>
- <channel id="DateMinHumidityThisMonth" typeId="dateMinHumidityThisMonth"/>
- <channel id="DateMaxHumidity" typeId="dateMaxHumidity"/>
- <channel id="DateMaxHumidityThisWeek" typeId="dateMaxHumidityThisWeek"/>
- <channel id="DateMaxHumidityThisMonth" typeId="dateMaxHumidityThisMonth"/>
- <channel id="Humidex" typeId="humidex"/>
- <channel id="HeatIndex" typeId="heatIndex"/>
- <channel id="Dewpoint" typeId="dewPoint"/>
- <channel id="DewpointDepression" typeId="dewPointDepression"/>
- <channel id="MinTemp" typeId="minTemp"/>
- <channel id="MinTempThisWeek" typeId="minTempThisWeek"/>
- <channel id="MinTempThisMonth" typeId="minTempThisMonth"/>
- <channel id="MaxTemp" typeId="maxTemp"/>
- <channel id="MaxTempThisWeek" typeId="maxTempThisWeek"/>
- <channel id="MaxTempThisMonth" typeId="maxTempThisMonth"/>
- <channel id="DateMinTemp" typeId="dateMinTemp"/>
- <channel id="DateMinTempThisWeek" typeId="dateMinTempThisWeek"/>
- <channel id="DateMinTempThisMonth" typeId="dateMinTempThisMonth"/>
- <channel id="DateMaxTemp" typeId="dateMaxTemp"/>
- <channel id="DateMaxTempThisWeek" typeId="dateMaxTempThisWeek"/>
- <channel id="DateMaxTempThisMonth" typeId="dateMaxTempThisMonth"/>
- <!-- Common to all modules -->
- <channel id="TimeStamp" typeId="timeUtc"/>
- <channel id="LastMessage" typeId="lastMessage"/>
- <channel id="LowBattery" typeId="system.low-battery"/>
- <channel id="BatteryVP" typeId="system.battery-level"/>
- <channel id="RfStatus" typeId="system.signal-strength"/>
- </channels>
-
- <properties>
- <property name="signalLevels">90,80,70,60</property>
- <property name="batteryLevels">4200,4920,6000</property>
- </properties>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:module"/>
- </thing-type>
-
-</thing:thing-descriptions>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
- xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
- <!-- Netatmo Thermostat and Relay/Plug -->
- <thing-type id="NAPlug">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Thermostat Relay/Plug</label>
- <description>This represents the thermostat relay</description>
-
- <channels>
- <channel id="ConnectedBoiler" typeId="connectedBoiler"/>
- <channel id="LastPlugSeen" typeId="lastPlugSeen"/>
- <channel id="LastBilan" typeId="lastBilan"/>
- <!-- Common to all devices -->
- <channel id="LastStatusStore" typeId="lastStatusStore"/>
- <channel id="WifiStatus" typeId="system.signal-strength"/>
- <channel id="Location" typeId="location"/>
- </channels>
-
- <properties>
- <property name="signalLevels">99,84,69,54</property>
- <property name="refreshPeriod">3600000</property>
- </properties>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:plug"/>
- </thing-type>
-
- <thing-type id="NATherm1">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Thermostat Module</label>
- <description>This represents the thermostat module itself</description>
-
- <channels>
- <channel id="Temperature" typeId="temperature"/>
- <channel id="Sp_Temperature" typeId="setpointTemp"/>
- <channel id="SetpointMode" typeId="setpointMode"/>
- <channel id="ThermRelayCmd" typeId="ThermRelayCmd"/>
- <channel id="ThermOrientation" typeId="ThermOrientation"/>
- <channel id="TimeStamp" typeId="timeUtc"/>
- <channel id="Planning" typeId="planning"/>
- <channel id="SetpointEndTime" typeId="setpointEndTime"/>
-
- <!-- Common to all modules -->
- <channel id="LastMessage" typeId="lastMessage"/>
- <channel id="LowBattery" typeId="system.low-battery"/>
- <channel id="BatteryVP" typeId="system.battery-level"/>
- <channel id="RfStatus" typeId="system.signal-strength"/>
- </channels>
-
- <properties>
- <property name="signalLevels">90,80,70,60</property>
- <property name="batteryLevels">2700,3300,4500</property>
- </properties>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:natherm1"/>
- </thing-type>
-
-</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="netatmo"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <channel-group-type id="temperature-extended">
+ <label>Temperature</label>
+ <channels>
+ <channel id="value" typeId="system.indoor-temperature"/>
+ <channel id="min-today" typeId="min-temp"/>
+ <channel id="max-today" typeId="max-temp"/>
+ <channel id="min-time" typeId="timestamp-advanced">
+ <label>Today Min Timestamp</label>
+ <description>Moment when temperature was measured at its minimum today.</description>
+ </channel>
+ <channel id="max-time" typeId="timestamp-advanced">
+ <label>Today Max Timestamp</label>
+ <description>Moment when temperature was measured at its maximum today.</description>
+ </channel>
+ <channel id="trend" typeId="trend">
+ <label>Temperature Trend</label>
+ </channel>
+ <channel id="heat-index" typeId="heat-index"/>
+ <channel id="dewpoint" typeId="dewpoint"/>
+ <channel id="dewpoint-depression" typeId="dewpoint-depression"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="temperature-outside">
+ <label>Temperature</label>
+ <channels>
+ <channel id="value" typeId="system.outdoor-temperature"/>
+ <channel id="min-today" typeId="min-temp"/>
+ <channel id="max-today" typeId="max-temp"/>
+ <channel id="min-time" typeId="timestamp-advanced">
+ <label>Today Min Timestamp</label>
+ <description>Moment when temperature was measured at its minimum today.</description>
+ </channel>
+ <channel id="max-time" typeId="timestamp-advanced">
+ <label>Today Max Timestamp</label>
+ <description>Moment when temperature was measured at its maximum today.</description>
+ </channel>
+ <channel id="trend" typeId="trend">
+ <label>Temperature Trend</label>
+ </channel>
+ <channel id="heat-index" typeId="heat-index"/>
+ <channel id="dewpoint" typeId="dewpoint"/>
+ <channel id="dewpoint-depression" typeId="dewpoint-depression"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="humidity">
+ <label>Humidity</label>
+ <channels>
+ <channel id="value" typeId="system.atmospheric-humidity"/>
+ <channel id="humidex" typeId="humidex"/>
+ <channel id="humidex-scale" typeId="humidex-scale"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="airquality">
+ <label>Air Quality</label>
+ <channels>
+ <channel id="co2" typeId="co2"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="noise">
+ <label>Noise</label>
+ <channels>
+ <channel id="value" typeId="noise"/>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="pressure-extended">
+ <label>Pressure</label>
+ <channels>
+ <channel id="value" typeId="system.barometric-pressure"/>
+ <channel id="absolute" typeId="absolute-pressure"/>
+ <channel id="trend" typeId="trend">
+ <label>Pressure Trend</label>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="rain">
+ <label>Rain gauge</label>
+ <channels>
+ <channel id="value" typeId="rain-intensity"/>
+ <channel id="sum-1" typeId="rain-quantity">
+ <label>Rain 1h</label>
+ <description>Quantity of water over last hour.</description>
+ </channel>
+ <channel id="sum-24" typeId="rain-quantity">
+ <label>Rain 24h</label>
+ <description>Quantity of water during the current day.</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <channel-group-type id="wind">
+ <label>Wind</label>
+ <channels>
+ <channel id="angle" typeId="system.wind-direction"/>
+ <channel id="strength" typeId="system.wind-speed"/>
+ <channel id="max-strength" typeId="max-wind-strength"/>
+ <channel id="max-strength-date" typeId="timestamp-advanced">
+ <label>Date Max Wind Strength</label>
+ <description>Moment when max wind strength was recorded.</description>
+ </channel>
+ <channel id="gust-angle" typeId="gust-angle"/>
+ <channel id="gust-strength" typeId="gust-strength"/>
+ </channels>
+ </channel-group-type>
+
+</thing:thing-descriptions>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
- xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
- <thing-type id="NAWelcomeHome">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Welcome Home</label>
- <description>This represents a home hosting a camera</description>
-
- <channels>
- <channel id="welcomeHomeCity" typeId="homecity"></channel>
- <channel id="welcomeHomeCountry" typeId="homecountry"></channel>
- <channel id="welcomeHomeTimezone" typeId="hometimezone"></channel>
-
- <channel id="welcomeHomePersonCount" typeId="homepersoncount"></channel>
- <channel id="welcomeHomeUnknownCount" typeId="homeunknowncount"></channel>
-
- <channel id="welcomeEventType" typeId="type"></channel>
- <channel id="welcomeEventTime" typeId="time"></channel>
- <channel id="welcomeEventCameraId" typeId="camera_id"></channel>
- <channel id="welcomeEventPersonId" typeId="person_id"></channel>
- <channel id="welcomeEventSnapshot" typeId="snapshot"></channel>
- <channel id="welcomeEventSnapshotURL" typeId="snapshot_url"></channel>
- <channel id="welcomeEventVideoURL" typeId="video_url"></channel>
- <channel id="welcomeEventVideoStatus" typeId="video_status"></channel>
- <channel id="welcomeEventIsArrival" typeId="is_arrival"></channel>
- <channel id="welcomeEventMessage" typeId="message"></channel>
- <channel id="welcomeEventSubType" typeId="sub_type"></channel>
- <channel id="welcomeHomeEvent" typeId="homeEvent"></channel>
-
- <channel id="cameraEvent" typeId="cameraEvent"/>
- </channels>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:welcomehome"/>
- </thing-type>
-
- <channel-type id="homecity">
- <item-type>String</item-type>
- <label>City</label>
- <description>City of the home</description>
- <state readOnly="true"></state>
- </channel-type>
- <channel-type id="homecountry">
- <item-type>String</item-type>
- <label>Country</label>
- <description>Country of the home</description>
- <state readOnly="false"></state>
- </channel-type>
- <channel-type id="hometimezone">
- <item-type>String</item-type>
- <label>Timezone</label>
- <description>Timezone of the home</description>
- <state readOnly="true"></state>
- </channel-type>
- <channel-type id="homepersoncount" advanced="false">
- <item-type>Number</item-type>
- <label>Person Counter</label>
- <description>Total number of Persons that are at home</description>
- <state readOnly="true" pattern="%d"></state>
- </channel-type>
- <channel-type id="homeunknowncount" advanced="true">
- <item-type>Number</item-type>
- <label>Unknown Person Counter</label>
- <description>Count how many Unknown Persons are at home</description>
- <state readOnly="true" pattern="%d"></state>
- </channel-type>
-
- <channel-type id="type">
- <item-type>String</item-type>
- <label>Type</label>
- <description>Type of event. Go to the Welcome page for further details.</description>
- <state readOnly="true"></state>
- </channel-type>
- <channel-type id="time">
- <item-type>DateTime</item-type>
- <label>Time</label>
- <description>Time of occurrence of event</description>
- <state readOnly="true"></state>
- </channel-type>
- <channel-type id="camera_id">
- <item-type>String</item-type>
- <label>Camera ID</label>
- <description>Camera that detected the event</description>
- <state readOnly="true"></state>
- </channel-type>
- <channel-type id="person_id">
- <item-type>String</item-type>
- <label>Person ID</label>
- <description>Id of the person the event is about (if any)</description>
- <state readOnly="true"></state>
- </channel-type>
- <channel-type id="snapshot_url" advanced="true">
- <item-type>String</item-type>
- <label>Snapshot URL</label>
- <description>Url of the event snapshot</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="snapshot">
- <item-type>Image</item-type>
- <label>Event Snapshot</label>
- <description>Event Snapshot</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="video_url" advanced="true">
- <item-type>String</item-type>
- <label>Video URL</label>
- <description>URL of the event video</description>
- <state readOnly="true"></state>
- </channel-type>
- <channel-type id="video_status">
- <item-type>String</item-type>
- <label>Video Status</label>
- <description>Status of the video (recording, deleted or available)</description>
- <state readOnly="true"></state>
- </channel-type>
- <channel-type id="is_arrival">
- <item-type>Switch</item-type>
- <label>Is Arrival</label>
- <description>If person was considered "away" before being seen during this event</description>
- <state readOnly="true"></state>
- </channel-type>
- <channel-type id="message">
- <item-type>String</item-type>
- <label>Message</label>
- <description>Message sent by Netatmo corresponding to given event</description>
- <state readOnly="true"></state>
- </channel-type>
- <channel-type id="sub_type" advanced="true">
- <item-type>String</item-type>
- <label>Sub Type</label>
- <description>Sub-type of SD and Alim events. Go to Welcome page for further details.</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="cameraEvent">
- <kind>trigger</kind>
- <label>Camera Event</label>
- <event>
- <options>
- <option value="ANIMAL">Animal detected</option>
- <option value="HUMAN">Human detected</option>
- <option value="MOVEMENT">Unspecified movement detected</option>
- <option value="VEHICLE">Vehicle detected</option>
- </options>
- </event>
- </channel-type>
-
-</thing:thing-descriptions>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="netatmo"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
- xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
- <thing-type id="NAWelcomePerson">
- <supported-bridge-type-refs>
- <bridge-type-ref id="netatmoapi"/>
- </supported-bridge-type-refs>
-
- <label>Welcome Person</label>
- <description>This represents a person at home</description>
-
- <channels>
- <channel id="welcomePersonLastSeen" typeId="last_seen"></channel>
- <channel id="welcomePersonAtHome" typeId="person_athome"></channel>
- <channel id="welcomePersonAvatarUrl" typeId="person_avatar_url"></channel>
- <channel id="welcomePersonAvatar" typeId="person_avatar"></channel>
- <channel id="welcomePersonLastEventMessage" typeId="person_eventmsg"></channel>
- <channel id="welcomePersonLastEventTime" typeId="person_eventtime"></channel>
- <channel id="welcomePersonLastEventUrl" typeId="person_event_url"></channel>
- <channel id="welcomePersonLastEvent" typeId="person_event"></channel>
- <channel id="welcomeHomeEvent" typeId="homeEvent"></channel>
- </channels>
-
- <representation-property>id</representation-property>
- <config-description-ref uri="thing-type:netatmo:nawelcomeperson"/>
- </thing-type>
-
- <channel-type id="last_seen">
- <item-type>DateTime</item-type>
- <label>Last Seen</label>
- <description>Time when this person was last seen</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="person_athome">
- <item-type>Switch</item-type>
- <label>At Home</label>
- <description>Indicates if this person is known to be at home or not</description>
- </channel-type>
-
- <channel-type id="person_eventmsg">
- <item-type>String</item-type>
- <label>Last Event Message</label>
- <description>Last Event message from this person</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="person_eventtime">
- <item-type>DateTime</item-type>
- <label>Last Event Time</label>
- <description>Last Event message time for this person</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="person_avatar_url" advanced="true">
- <item-type>String</item-type>
- <label>Avatar URL</label>
- <description>URL for the avatar of this person</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="person_avatar">
- <item-type>Image</item-type>
- <label>Avatar</label>
- <description>Avatar of this person</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="person_event">
- <item-type>Image</item-type>
- <label>Last Event Picture</label>
- <description>Picture of the last event for this person</description>
- <state readOnly="true"></state>
- </channel-type>
-
- <channel-type id="person_event_url" advanced="true">
- <item-type>String</item-type>
- <label>Last Event URL</label>
- <description>URL for the picture of the last event for this person</description>
- <state readOnly="true"></state>
- </channel-type>
-
-</thing:thing-descriptions>
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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());
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 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());
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.discovery;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.*;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.quality.Strictness;
-import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.i18n.TranslationProvider;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ThingUID;
-
-import io.swagger.client.model.NAMain;
-import io.swagger.client.model.NAStationDataBody;
-import io.swagger.client.model.NAStationModule;
-
-/**
- * @author Sven Strohschein - Initial contribution
- */
-@ExtendWith(MockitoExtension.class)
-@MockitoSettings(strictness = Strictness.LENIENT)
-public class NetatmoModuleDiscoveryServiceTest {
-
- private NetatmoModuleDiscoveryServiceAccessible service;
- private NetatmoBridgeHandler bridgeHandlerSpy;
-
- @BeforeEach
- public void before() {
- Bridge bridgeMock = mock(Bridge.class);
- when(bridgeMock.getUID()).thenReturn(new ThingUID("netatmo", "bridge"));
-
- bridgeHandlerSpy = spy(new NetatmoBridgeHandler(bridgeMock, null));
-
- LocaleProvider localeProviderMock = mock(LocaleProvider.class);
- TranslationProvider translationProvider = mock(TranslationProvider.class);
-
- service = new NetatmoModuleDiscoveryServiceAccessible(bridgeHandlerSpy, localeProviderMock,
- translationProvider);
- }
-
- @Test
- public void testStartScanNothingActivated() {
- service.startScan();
-
- assertEquals(0, service.getDiscoveredThings().size());
- }
-
- @Test
- public void testStartScanDiscoverWeatherStationNoStationsBody() {
- activateDiscoveryWeatherStation();
-
- service.startScan();
-
- assertEquals(0, service.getDiscoveredThings().size());
- }
-
- @Test
- public void testStartScanDiscoverWeatherStationNoStations() {
- activateDiscoveryWeatherStation();
-
- when(bridgeHandlerSpy.getStationsDataBody(null)).thenReturn(Optional.of(new NAStationDataBody()));
- service.startScan();
-
- assertEquals(0, service.getDiscoveredThings().size());
- }
-
- @Test
- public void testStartScanDiscoverWeatherStationNoStationName() {
- recordStationBody(createStation());
-
- service.startScan();
-
- List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
- assertEquals(1, discoveredThings.size());
- // Expected is only the type name, because a station name isn't available
- assertEquals("NAMain", discoveredThings.get(0).getLabel());
- }
-
- @Test
- public void testStartScanDiscoverWeatherStation() {
- NAMain station = createStation();
- station.setStationName("Neu Wulmstorf");
-
- recordStationBody(station);
-
- service.startScan();
-
- List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
- assertEquals(1, discoveredThings.size());
- // Expected is the type name + station name, because both are available
- // and the station name contains only the city name by default which wouldn't be sufficient.
- assertEquals("NAMain Neu Wulmstorf", discoveredThings.get(0).getLabel());
- }
-
- @Test
- public void testStartScanDiscoverWeatherStationNoStationNameFavorite() {
- NAMain station = createStation();
- station.setFavorite(true);
-
- recordStationBody(station);
-
- service.startScan();
-
- List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
- assertEquals(1, discoveredThings.size());
- // Expected is "(favorite)" within the label to make clear that it is favorite station
- // (and not the station of the user)
- assertEquals("NAMain (favorite)", discoveredThings.get(0).getLabel());
- }
-
- @Test
- public void testStartScanDiscoverWeatherStationFavorite() {
- NAMain station = createStation();
- station.setStationName("Neu Wulmstorf");
- station.setFavorite(true);
-
- recordStationBody(station);
-
- service.startScan();
-
- List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
- assertEquals(1, discoveredThings.size());
- // Expected is "(favorite)" within the label to make clear that it is favorite station
- // (and not the station of the user)
- assertEquals("NAMain Neu Wulmstorf (favorite)", discoveredThings.get(0).getLabel());
- }
-
- @Test
- public void testStartScanDiscoverWeatherStationModuleNoModuleName() {
- NAMain station = createStation(createModule());
- station.setStationName("Neu Wulmstorf");
-
- recordStationBody(station);
-
- service.startScan();
-
- List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
- assertEquals(2, discoveredThings.size());
- assertEquals("NAMain Neu Wulmstorf", discoveredThings.get(0).getLabel());
- // Expected is the type name + station name to make clear that the module belongs to the station.
- // The module name isn't available, therefore the type name of the module is used.
- assertEquals("NAModule1 Neu Wulmstorf", discoveredThings.get(1).getLabel());
- }
-
- @Test
- public void testStartScanDiscoverWeatherStationModule() {
- NAStationModule module = createModule();
- module.setModuleName("Outdoor-Module");
-
- NAMain station = createStation(module);
- station.setStationName("Neu Wulmstorf");
-
- recordStationBody(station);
-
- service.startScan();
-
- List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
- assertEquals(2, discoveredThings.size());
- assertEquals("NAMain Neu Wulmstorf", discoveredThings.get(0).getLabel());
- // Expected is the module name + station name to make clear that the module belongs to the station.
- // Because an explicit module name is available, the module type name isn't required.
- assertEquals("Outdoor-Module Neu Wulmstorf", discoveredThings.get(1).getLabel());
- }
-
- @Test
- public void testStartScanDiscoverWeatherStationModuleNoModuleNameFavorite() {
- NAMain station = createStation(createModule());
- station.setStationName("Neu Wulmstorf");
- station.setFavorite(true);
-
- recordStationBody(station);
-
- service.startScan();
-
- List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
- assertEquals(2, discoveredThings.size());
- assertEquals("NAMain Neu Wulmstorf (favorite)", discoveredThings.get(0).getLabel());
- // Expected is "(favorite)" within the label to make clear that it is favorite station
- // (and not the station of the user)
- assertEquals("NAModule1 Neu Wulmstorf (favorite)", discoveredThings.get(1).getLabel());
- }
-
- @Test
- public void testStartScanDiscoverWeatherStationModuleFavorite() {
- NAStationModule module = createModule();
- module.setModuleName("Outdoor-Module");
-
- NAMain station = createStation(module);
- station.setStationName("Neu Wulmstorf");
- station.setFavorite(true);
-
- recordStationBody(station);
-
- service.startScan();
-
- List<DiscoveryResult> discoveredThings = service.getDiscoveredThings();
- assertEquals(2, discoveredThings.size());
- assertEquals("NAMain Neu Wulmstorf (favorite)", discoveredThings.get(0).getLabel());
- // Expected is "(favorite)" within the label to make clear that it is favorite station
- // (and not the station of the user)
- assertEquals("Outdoor-Module Neu Wulmstorf (favorite)", discoveredThings.get(1).getLabel());
- }
-
- private void recordStationBody(NAMain station) {
- activateDiscoveryWeatherStation();
-
- NAStationDataBody stationsBody = new NAStationDataBody();
- stationsBody.setDevices(Collections.singletonList(station));
-
- when(bridgeHandlerSpy.getStationsDataBody(null)).thenReturn(Optional.of(stationsBody));
- }
-
- private void activateDiscoveryWeatherStation() {
- bridgeHandlerSpy.configuration.readStation = true;
- }
-
- private static NAMain createStation() {
- NAMain station = new NAMain();
- station.setId("01:00:00:00:00:aa");
- station.setType("NAMain");
- return station;
- }
-
- private static NAMain createStation(NAStationModule module) {
- NAMain station = createStation();
- station.setModules(Collections.singletonList(module));
- return station;
- }
-
- private static NAStationModule createModule() {
- NAStationModule module = new NAStationModule();
- module.setId("01:00:00:00:01:aa");
- module.setType("NAModule1");
- return module;
- }
-
- @NonNullByDefault
- private static class NetatmoModuleDiscoveryServiceAccessible extends NetatmoModuleDiscoveryService {
-
- private final List<DiscoveryResult> discoveredThings;
-
- private NetatmoModuleDiscoveryServiceAccessible(NetatmoBridgeHandler netatmoBridgeHandler,
- LocaleProvider localeProvider, TranslationProvider translationProvider) {
- super(netatmoBridgeHandler, localeProvider, translationProvider);
- discoveredThings = new ArrayList<>();
- }
-
- @Override
- protected void thingDiscovered(DiscoveryResult discoveryResult) {
- super.thingDiscovered(discoveryResult);
- discoveredThings.add(discoveryResult);
- }
-
- private List<DiscoveryResult> getDiscoveredThings() {
- return discoveredThings;
- }
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.presence;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
-
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.quality.Strictness;
-import org.openhab.binding.netatmo.internal.NetatmoBindingConstants;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.internal.ThingImpl;
-import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-import io.swagger.client.model.NAWelcomeCamera;
-
-/**
- * @author Sven Strohschein - Initial contribution
- */
-@ExtendWith(MockitoExtension.class)
-@MockitoSettings(strictness = Strictness.LENIENT)
-public class NAPresenceCameraHandlerTest {
-
- private static final String DUMMY_VPN_URL = "https://dummytestvpnaddress.net/restricted/10.255.89.96/9826069dc689e8327ac3ed2ced4ff089/MTU5MTgzMzYwMDrQ7eHHhG0_OJ4TgmPhGlnK7QQ5pZ,,";
- private static final String DUMMY_LOCAL_URL = "http://192.168.178.76/9826069dc689e8327ac3ed2ced4ff089";
- private static final Optional<String> DUMMY_PING_RESPONSE = createPingResponseContent(DUMMY_LOCAL_URL);
-
- private @Mock RequestExecutor requestExecutorMock;
- private @Mock TimeZoneProvider timeZoneProviderMock;
-
- private Thing presenceCameraThing;
- private NAWelcomeCamera presenceCamera;
- private ChannelUID cameraStatusChannelUID;
- private ChannelUID floodlightChannelUID;
- private ChannelUID floodlightAutoModeChannelUID;
- private NAPresenceCameraHandlerAccessible handler;
-
- @BeforeEach
- public void before() {
- presenceCameraThing = new ThingImpl(new ThingTypeUID("netatmo", "NOC"), "1");
- presenceCamera = new NAWelcomeCamera();
-
- cameraStatusChannelUID = new ChannelUID(presenceCameraThing.getUID(),
- NetatmoBindingConstants.CHANNEL_CAMERA_STATUS);
- floodlightChannelUID = new ChannelUID(presenceCameraThing.getUID(),
- NetatmoBindingConstants.CHANNEL_CAMERA_FLOODLIGHT);
- floodlightAutoModeChannelUID = new ChannelUID(presenceCameraThing.getUID(),
- NetatmoBindingConstants.CHANNEL_CAMERA_FLOODLIGHT_AUTO_MODE);
-
- handler = new NAPresenceCameraHandlerAccessible(presenceCameraThing, presenceCamera);
- }
-
- @Test
- public void testHandleCommandSwitchSurveillanceOn() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(cameraStatusChannelUID, OnOffType.ON);
-
- verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch on
- verify(requestExecutorMock).executeGETRequest(DUMMY_LOCAL_URL + "/command/changestatus?status=on");
- }
-
- @Test
- public void testHandleCommandSwitchSurveillanceOff() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(cameraStatusChannelUID, OnOffType.OFF);
-
- verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
- verify(requestExecutorMock).executeGETRequest(DUMMY_LOCAL_URL + "/command/changestatus?status=off");
- }
-
- @Test
- public void testHandleCommandSwitchSurveillanceUnknownCommand() {
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(cameraStatusChannelUID, RefreshType.REFRESH);
-
- verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
- // command
- }
-
- @Test
- public void testHandleCommandSwitchSurveillanceWithoutVPN() {
- handler.handleCommand(cameraStatusChannelUID, OnOffType.ON);
-
- verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed when no VPN
- // address is set
- }
-
- @Test
- public void testHandleCommandSwitchFloodlightOn() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
- verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch on
- verify(requestExecutorMock)
- .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
- }
-
- @Test
- public void testHandleCommandSwitchFloodlightOff() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
-
- verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
- verify(requestExecutorMock).executeGETRequest(
- DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
- }
-
- @Test
- public void testHandleCommandSwitchFloodlightOffWithAutoModeOn() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
- assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
-
- handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
-
- verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
- verify(requestExecutorMock).executeGETRequest(
- DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22auto%22%7D");
- }
-
- @Test
- public void testHandleCommandSwitchFloodlightOnAddressChanged() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(floodlightChannelUID, OnOffType.ON);
- // 1.) execute ping + 2.) execute switch on
- verify(requestExecutorMock, times(2)).executeGETRequest(any());
- verify(requestExecutorMock)
- .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
-
- handler.handleCommand(floodlightChannelUID, OnOffType.OFF);
- // 1.) execute ping + 2.) execute switch on + 3.) execute switch off
- verify(requestExecutorMock, times(3)).executeGETRequest(any());
- verify(requestExecutorMock)
- .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
- verify(requestExecutorMock).executeGETRequest(
- DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
-
- final String newDummyVPNURL = DUMMY_VPN_URL + "2";
- final String newDummyLocalURL = DUMMY_LOCAL_URL + "2";
- final Optional<String> newDummyPingResponse = createPingResponseContent(newDummyLocalURL);
-
- when(requestExecutorMock.executeGETRequest(newDummyVPNURL + "/command/ping")).thenReturn(newDummyPingResponse);
-
- presenceCamera.setVpnUrl(newDummyVPNURL);
- handler.handleCommand(floodlightChannelUID, OnOffType.ON);
- // 1.) execute ping + 2.) execute switch on + 3.) execute switch off + 4.) execute ping + 5.) execute switch on
- verify(requestExecutorMock, times(5)).executeGETRequest(any());
- verify(requestExecutorMock)
- .executeGETRequest(DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
- verify(requestExecutorMock).executeGETRequest(
- DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
- verify(requestExecutorMock).executeGETRequest(
- newDummyLocalURL + "/command/floodlight_set_config?config=%7B%22mode%22:%22on%22%7D");
- }
-
- @Test
- public void testHandleCommandSwitchFloodlightUnknownCommand() {
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(floodlightChannelUID, RefreshType.REFRESH);
-
- verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
- // command
- }
-
- @Test
- public void testHandleCommandSwitchFloodlightAutoModeOn() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-
- handler.handleCommand(floodlightAutoModeChannelUID, OnOffType.ON);
-
- verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch
- // auto-mode on
- verify(requestExecutorMock).executeGETRequest(
- DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22auto%22%7D");
- }
-
- @Test
- public void testHandleCommandSwitchFloodlightAutoModeOff() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(DUMMY_PING_RESPONSE);
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-
- handler.handleCommand(floodlightAutoModeChannelUID, OnOffType.OFF);
-
- verify(requestExecutorMock, times(2)).executeGETRequest(any()); // 1.) execute ping + 2.) execute switch off
- verify(requestExecutorMock).executeGETRequest(
- DUMMY_LOCAL_URL + "/command/floodlight_set_config?config=%7B%22mode%22:%22off%22%7D");
- }
-
- @Test
- public void testHandleCommandSwitchFloodlightAutoModeUnknownCommand() {
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(floodlightAutoModeChannelUID, RefreshType.REFRESH);
-
- verify(requestExecutorMock, never()).executeGETRequest(any()); // nothing should get executed on a refresh
- // command
- }
-
- /**
- * The request "fails" because there is no response content of the ping command.
- */
- @Test
- public void testHandleCommandRequestFailed() {
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
- verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
- }
-
- @Test
- public void testHandleCommandWithoutVPN() {
- handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
- verify(requestExecutorMock, never()).executeGETRequest(any()); // no executions because the VPN URL is still
- // unknown
- }
-
- @Test
- public void testHandleCommandPingFailedNULLResponse() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(Optional.of(""));
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
- verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
- }
-
- @Test
- public void testHandleCommandPingFailedEmptyResponse() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping")).thenReturn(Optional.of(""));
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
- verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
- }
-
- @Test
- public void testHandleCommandPingFailedWrongResponse() {
- when(requestExecutorMock.executeGETRequest(DUMMY_VPN_URL + "/command/ping"))
- .thenReturn(Optional.of("{ \"message\": \"error\" }"));
-
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- handler.handleCommand(floodlightChannelUID, OnOffType.ON);
-
- verify(requestExecutorMock, times(1)).executeGETRequest(any()); // 1.) execute ping
- }
-
- @Test
- public void testHandleCommandModuleNULL() {
- NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
- timeZoneProviderMock);
- handlerWithoutModule.handleCommand(floodlightChannelUID, OnOffType.ON);
-
- verify(requestExecutorMock, never()).executeGETRequest(any()); // no executions because the thing isn't
- // initialized
- }
-
- @Test
- public void testGetNAThingPropertyCommonChannel() {
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_CAMERA_STATUS));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightOn() {
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
- assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightOff() {
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightAuto() {
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
- // When the floodlight is set to auto-mode it is currently off.
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightWithoutLightModeState() {
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightModuleNULL() {
- NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
- timeZoneProviderMock);
- assertEquals(UnDefType.UNDEF, handlerWithoutModule.getNAThingProperty(floodlightChannelUID.getId()));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightAutoModeFloodlightAuto() {
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
- assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightAutoModeFloodlightOn() {
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
- // When the floodlight is initially on (on starting the binding), there is no information about if the auto-mode
- // was set before. Therefore the auto-mode is detected as deactivated / off.
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightAutoModeFloodlightOff() {
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
- // When the floodlight is initially off (on starting the binding), the auto-mode isn't set.
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightScenarioWithAutoMode() {
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.AUTO);
- assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
-
- // The auto-mode was initially set, after that the floodlight was switched on by the user.
- // In this case the binding should still know that the auto-mode is/was set.
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
- assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
- assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
-
- // After that the user switched off the floodlight.
- // In this case the binding should still know that the auto-mode is/was set.
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
- assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightScenarioWithoutAutoMode() {
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
-
- // The auto-mode wasn't set, after that the floodlight was switched on by the user.
- // In this case the binding should still know that the auto-mode isn't/wasn't set.
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.ON);
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
- assertEquals(OnOffType.ON, handler.getNAThingProperty(floodlightChannelUID.getId()));
-
- // After that the user switched off the floodlight.
- // In this case the binding should still know that the auto-mode isn't/wasn't set.
- presenceCamera.setLightModeStatus(NAWelcomeCamera.LightModeStatusEnum.OFF);
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
- assertEquals(OnOffType.OFF, handler.getNAThingProperty(floodlightChannelUID.getId()));
- }
-
- @Test
- public void testGetNAThingPropertyFloodlightAutoModeModuleNULL() {
- NAPresenceCameraHandler handlerWithoutModule = new NAPresenceCameraHandler(presenceCameraThing,
- timeZoneProviderMock);
- assertEquals(UnDefType.UNDEF, handlerWithoutModule.getNAThingProperty(floodlightAutoModeChannelUID.getId()));
- }
-
- @Test
- public void testGetStreamURL() {
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
- assertTrue(streamURL.isPresent());
- assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index.m3u8", streamURL.get());
- }
-
- @Test
- public void testGetStreamURLLocal() {
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- presenceCamera.setIsLocal(true);
-
- Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
- assertTrue(streamURL.isPresent());
- assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index_local.m3u8", streamURL.get());
- }
-
- @Test
- public void testGetStreamURLNotLocal() {
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
- presenceCamera.setIsLocal(false);
-
- Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
- assertTrue(streamURL.isPresent());
- assertEquals(DUMMY_VPN_URL + "/vod/dummyVideoId/index.m3u8", streamURL.get());
- }
-
- @Test
- public void testGetStreamURLWithoutVPN() {
- Optional<String> streamURL = handler.getStreamURL("dummyVideoId");
- assertFalse(streamURL.isPresent());
- }
-
- @Test
- public void testGetLivePictureURLState() {
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-
- State livePictureURLState = handler.getLivePictureURLState();
- assertEquals(new StringType(DUMMY_VPN_URL + "/live/snapshot_720.jpg"), livePictureURLState);
- }
-
- @Test
- public void testGetLivePictureURLStateWithoutVPN() {
- State livePictureURLState = handler.getLivePictureURLState();
- assertEquals(UnDefType.UNDEF, livePictureURLState);
- }
-
- @Test
- public void testGetLiveStreamState() {
- presenceCamera.setVpnUrl(DUMMY_VPN_URL);
-
- State liveStreamState = handler.getLiveStreamState();
- assertEquals(new StringType(DUMMY_VPN_URL + "/live/index.m3u8"), liveStreamState);
- }
-
- @Test
- public void testGetLiveStreamStateWithoutVPN() {
- State liveStreamState = handler.getLiveStreamState();
- assertEquals(UnDefType.UNDEF, liveStreamState);
- }
-
- private static Optional<String> createPingResponseContent(final String localURL) {
- return Optional.of("{\"local_url\":\"" + localURL + "\",\"product_name\":\"Welcome Netatmo\"}");
- }
-
- private interface RequestExecutor {
-
- Optional<String> executeGETRequest(String url);
- }
-
- private class NAPresenceCameraHandlerAccessible extends NAPresenceCameraHandler {
-
- private NAPresenceCameraHandlerAccessible(Thing thing, NAWelcomeCamera presenceCamera) {
- super(thing, timeZoneProviderMock);
- setModule(presenceCamera);
- }
-
- @Override
- protected @NonNull Optional<@NonNull String> executeGETRequest(@NonNull String url) {
- return requestExecutorMock.executeGETRequest(url);
- }
-
- @Override
- protected @NonNull State getLivePictureURLState() {
- return super.getLivePictureURLState();
- }
-
- @Override
- protected @NonNull State getLiveStreamState() {
- return super.getLiveStreamState();
- }
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.netatmo.internal.welcome;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.quality.Strictness;
-import org.openhab.binding.netatmo.internal.NetatmoBindingConstants;
-import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
-import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
-import org.openhab.core.i18n.TimeZoneProvider;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.internal.ThingImpl;
-import org.openhab.core.types.UnDefType;
-
-import io.swagger.client.model.NAWelcomeEvent;
-import io.swagger.client.model.NAWelcomeHome;
-import io.swagger.client.model.NAWelcomeHomeData;
-import io.swagger.client.model.NAWelcomeSubEvent;
-
-/**
- * @author Sven Strohschein - Initial contribution
- */
-@ExtendWith(MockitoExtension.class)
-@MockitoSettings(strictness = Strictness.LENIENT)
-public class NAWelcomeHomeHandlerTest {
-
- private static final String DUMMY_HOME_ID = "1";
-
- private NAWelcomeHomeHandlerAccessible handler;
-
- private @Mock NetatmoBridgeHandler bridgeHandlerMock;
- private @Mock TimeZoneProvider timeZoneProviderMock;
-
- @BeforeEach
- public void before() {
- Thing welcomeHomeThing = new ThingImpl(new ThingTypeUID("netatmo", "NAWelcomeHome"), "1");
- handler = new NAWelcomeHomeHandlerAccessible(welcomeHomeThing);
- }
-
- @Test
- public void testUpdateReadingsWithEvents() {
- NAWelcomeEvent event1 = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.PERSON);
- NAWelcomeEvent event2 = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
-
- NAWelcomeHome home = new NAWelcomeHome();
- home.setId(DUMMY_HOME_ID);
- home.setEvents(Arrays.asList(event1, event2));
-
- NAWelcomeHomeData homeData = new NAWelcomeHomeData();
- homeData.setHomes(Collections.singletonList(home));
-
- when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
- handler.updateReadings();
-
- // the second (last) event is expected
- assertEquals(new StringType("movement"),
- handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
-
- home.setEvents(Arrays.asList(event2, event1));
- // the second (last) event is still expected (independent from the order of these are added)
- assertEquals(new StringType("movement"),
- handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- }
-
- @Test
- public void testUpdateReadingsWith1Event() {
- NAWelcomeEvent event = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.PERSON);
-
- NAWelcomeHome home = new NAWelcomeHome();
- home.setId(DUMMY_HOME_ID);
- home.setEvents(Collections.singletonList(event));
-
- NAWelcomeHomeData homeData = new NAWelcomeHomeData();
- homeData.setHomes(Collections.singletonList(home));
-
- when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
- handler.updateReadings();
-
- assertEquals(new StringType("person"),
- handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- }
-
- @Test
- public void testUpdateReadingsNoEvents() {
- NAWelcomeHome home = new NAWelcomeHome();
- home.setId(DUMMY_HOME_ID);
-
- NAWelcomeHomeData homeData = new NAWelcomeHomeData();
- homeData.setHomes(Collections.singletonList(home));
-
- when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
- handler.updateReadings();
-
- assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- }
-
- @Test
- public void testUpdateReadingsEmptyHomeData() {
- NAWelcomeHomeData homeData = new NAWelcomeHomeData();
-
- when(bridgeHandlerMock.getWelcomeDataBody(any())).thenReturn(Optional.of(homeData));
-
- handler.updateReadings();
-
- assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- }
-
- @Test
- public void testUpdateReadingsNoHomeData() {
- handler.updateReadings();
-
- assertEquals(UnDefType.UNDEF, handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- }
-
- @Test
- public void testTriggerChannelIfRequired() {
- NAWelcomeEvent event1 = createPresenceEvent(1592661881, NAWelcomeSubEvent.TypeEnum.ANIMAL);
- NAWelcomeEvent event2 = createPresenceEvent(1592661882, NAWelcomeSubEvent.TypeEnum.HUMAN);
- NAWelcomeEvent event3 = createEvent(1592661883, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
-
- NAWelcomeHome home = new NAWelcomeHome();
- home.setId(DUMMY_HOME_ID);
- home.setEvents(Collections.singletonList(event1));
-
- NAWelcomeHomeData homeData = new NAWelcomeHomeData();
- homeData.setHomes(Collections.singletonList(home));
-
- when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
- triggerCameraEvents();
-
- // No triggered event is expected, because the binding is just started (with existing events).
- assertEquals(0, handler.getTriggerChannelCount());
-
- home.setEvents(Arrays.asList(event1, event2));
-
- triggerCameraEvents();
-
- // 1 triggered event is expected, because there is 1 new event since binding start (outdoor / detected human).
- assertEquals(1, handler.getTriggerChannelCount());
- assertEquals(new StringType("outdoor"),
- handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- assertEquals("HUMAN", handler.getLastDetectedObject());
-
- home.setEvents(Arrays.asList(event1, event2));
-
- triggerCameraEvents();
-
- // No new triggered event is expected, because there are still the same events as before the refresh.
- assertEquals(1, handler.getTriggerChannelCount());
- assertEquals(new StringType("outdoor"),
- handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- assertEquals("HUMAN", handler.getLastDetectedObject());
-
- home.setEvents(Arrays.asList(event1, event2, event3));
-
- triggerCameraEvents();
-
- // 1 new triggered event is expected (2 in sum), because there is 1 new event since the last triggered event
- // (movement after outdoor / detected human).
- assertEquals(2, handler.getTriggerChannelCount());
- assertEquals(new StringType("movement"),
- handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- assertEquals("MOVEMENT", handler.getLastDetectedObject());
- }
-
- @Test
- public void testTriggerChannelIfRequiredNoEventAvailable() {
- NAWelcomeHome home = new NAWelcomeHome();
- home.setId(DUMMY_HOME_ID);
-
- NAWelcomeHomeData homeData = new NAWelcomeHomeData();
- homeData.setHomes(Collections.singletonList(home));
-
- when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
- triggerCameraEvents();
-
- // No triggered event is expected, because there aren't any events (the collection is NULL)
- assertEquals(0, handler.getTriggerChannelCount());
-
- home.setEvents(Collections.emptyList());
-
- triggerCameraEvents();
-
- // No triggered event is expected, because there aren't any events (the collection is empty)
- assertEquals(0, handler.getTriggerChannelCount());
- }
-
- @Test
- public void testTriggerChannelIfRequiredPersonMovement() {
- NAWelcomeHome home = initHome();
-
- NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
- event.setPersonId("1");
-
- home.getEvents().add(event);
-
- triggerCameraEvents();
-
- assertEquals(1, handler.getTriggerChannelCount());
- assertEquals(new StringType("movement"),
- handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- assertEquals("HUMAN", handler.getLastDetectedObject());
- }
-
- @Test
- public void testTriggerChannelIfRequiredHumanMovement() {
- NAWelcomeHome home = initHome();
-
- NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
- event.setCategory(NAWelcomeEvent.CategoryEnum.HUMAN);
-
- home.getEvents().add(event);
-
- triggerCameraEvents();
-
- assertEquals(1, handler.getTriggerChannelCount());
- assertEquals(new StringType("movement"),
- handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- assertEquals("HUMAN", handler.getLastDetectedObject());
- }
-
- @Test
- public void testTriggerChannelIfRequiredAnimalMovement() {
- NAWelcomeHome home = initHome();
-
- NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
- event.setCategory(NAWelcomeEvent.CategoryEnum.ANIMAL);
-
- home.getEvents().add(event);
-
- triggerCameraEvents();
-
- assertEquals(1, handler.getTriggerChannelCount());
- assertEquals(new StringType("movement"),
- handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- assertEquals("ANIMAL", handler.getLastDetectedObject());
- }
-
- @Test
- public void testTriggerChannelIfRequiredVehicleMovement() {
- NAWelcomeHome home = initHome();
-
- NAWelcomeEvent event = createEvent(1592661882, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
- event.setCategory(NAWelcomeEvent.CategoryEnum.VEHICLE);
-
- home.getEvents().add(event);
-
- triggerCameraEvents();
-
- assertEquals(1, handler.getTriggerChannelCount());
- assertEquals(new StringType("movement"),
- handler.getNAThingProperty(NetatmoBindingConstants.CHANNEL_WELCOME_EVENT_TYPE));
- assertEquals("VEHICLE", handler.getLastDetectedObject());
- }
-
- @Test
- public void testMatchDetectedObjectEnums() {
- assertArrayEquals(Arrays.stream(NAWelcomeEvent.CategoryEnum.values()).map(Enum::name).toArray(),
- Arrays.stream(NAWelcomeSubEvent.TypeEnum.values()).map(Enum::name).toArray(),
- "The detected object enums aren't equal anymore, that could lead to a bug! Please check the usages!");
- }
-
- private NAWelcomeHome initHome() {
- NAWelcomeEvent initLastEvent = createEvent(1592661881, NAWebhookCameraEvent.EventTypeEnum.MOVEMENT);
-
- NAWelcomeHome home = new NAWelcomeHome();
- home.setId(DUMMY_HOME_ID);
-
- List<NAWelcomeEvent> events = new ArrayList<>();
- events.add(initLastEvent);
- home.setEvents(events);
-
- NAWelcomeHomeData homeData = new NAWelcomeHomeData();
- homeData.setHomes(Collections.singletonList(home));
-
- when(bridgeHandlerMock.getWelcomeDataBody(DUMMY_HOME_ID)).thenReturn(Optional.of(homeData));
-
- triggerCameraEvents();
-
- return home;
- }
-
- private void triggerCameraEvents() {
- handler.updateReadings();
- handler.triggerChannelIfRequired(NetatmoBindingConstants.CHANNEL_CAMERA_EVENT);
- }
-
- private static NAWelcomeEvent createPresenceEvent(int eventTime, NAWelcomeSubEvent.TypeEnum detectedObjectType) {
- NAWelcomeSubEvent subEvent = new NAWelcomeSubEvent();
- subEvent.setTime(eventTime);
- subEvent.setType(detectedObjectType);
-
- NAWelcomeEvent event = createEvent(eventTime, NAWebhookCameraEvent.EventTypeEnum.OUTDOOR);
- event.setEventList(Collections.singletonList(subEvent));
- return event;
- }
-
- private static NAWelcomeEvent createEvent(int eventTime, NAWebhookCameraEvent.EventTypeEnum eventType) {
- NAWelcomeEvent event = new NAWelcomeEvent();
- event.setType(eventType.toString());
- event.setTime(eventTime);
- return event;
- }
-
- private class NAWelcomeHomeHandlerAccessible extends NAWelcomeHomeHandler {
-
- private int triggerChannelCount;
- private String lastDetectedObject;
-
- private NAWelcomeHomeHandlerAccessible(Thing thing) {
- super(thing, timeZoneProviderMock);
- }
-
- @Override
- protected Optional<NetatmoBridgeHandler> getBridgeHandler() {
- return Optional.of(bridgeHandlerMock);
- }
-
- @Override
- protected String getId() {
- return DUMMY_HOME_ID;
- }
-
- @Override
- protected void triggerChannel(@NonNull String channelID, @NonNull String event) {
- triggerChannelCount++;
- lastDetectedObject = event;
- super.triggerChannel(channelID, event);
- }
-
- private int getTriggerChannelCount() {
- return triggerChannelCount;
- }
-
- public String getLastDetectedObject() {
- return lastDetectedObject;
- }
- }
-}