[nest] Remove WWN support (#15418)
See: https://support.google.com/googlenest/answer/9293712?hl=en > Starting September 29, 2023, all Works with Nest connections will stop working. Closes #13525 Closes #14761 Signed-off-by: Wouter Born <github@maindrain.net>
This commit is contained in:
parent
c5739eccc9
commit
5a803961d0
|
@ -443,7 +443,6 @@
|
||||||
/itests/org.openhab.binding.mqtt.homeassistant.tests/ @davidgraeff
|
/itests/org.openhab.binding.mqtt.homeassistant.tests/ @davidgraeff
|
||||||
/itests/org.openhab.binding.mqtt.homie.tests/ @davidgraeff
|
/itests/org.openhab.binding.mqtt.homie.tests/ @davidgraeff
|
||||||
/itests/org.openhab.binding.mqtt.ruuvigateway.tests/ @ssalonen
|
/itests/org.openhab.binding.mqtt.ruuvigateway.tests/ @ssalonen
|
||||||
/itests/org.openhab.binding.nest.tests/ @wborn
|
|
||||||
/itests/org.openhab.binding.ntp.tests/ @marcelrv
|
/itests/org.openhab.binding.ntp.tests/ @marcelrv
|
||||||
/itests/org.openhab.binding.systeminfo.tests/ @svilenvul
|
/itests/org.openhab.binding.systeminfo.tests/ @svilenvul
|
||||||
/itests/org.openhab.binding.tradfri.tests/ @cweitkamp @kaikreuzer
|
/itests/org.openhab.binding.tradfri.tests/ @cweitkamp @kaikreuzer
|
||||||
|
|
|
@ -1,51 +1,41 @@
|
||||||
# Nest Binding
|
# Nest Binding
|
||||||
|
|
||||||
The Nest binding integrates devices by [Nest](https://store.google.com/us/category/connected_home?) using the [Smart Device Management](https://developers.google.com/nest/device-access/api) (SDM) API and the Works with Nest (WWN) API.
|
The Nest binding integrates devices by [Nest](https://store.google.com/us/category/connected_home?) using the [Smart Device Management](https://developers.google.com/nest/device-access/api) (SDM) API.
|
||||||
|
|
||||||
To be able to use the SDM API it is required to first [register](https://developers.google.com/nest/device-access/registration) and pay a US$5 non-refundable registration fee.
|
To be able to use the SDM API it is required to first [register](https://developers.google.com/nest/device-access/registration) and pay a US$5 non-refundable registration fee.
|
||||||
|
|
||||||
It is also possible to use the older WWN API with this binding.
|
Because the SDM API runs on servers in the cloud, a connection with the Internet is required for sending and receiving information.
|
||||||
For this you need to have the account details of a previously registered WWN API account.
|
|
||||||
Another requirement is that you have not yet migrated your Nest account to a Google account (which is irreversible).
|
|
||||||
It is no longer possible to register new WWN API accounts because the WWN API runs in maintenance mode.
|
|
||||||
See also [What's happening at Nest?](https://nest.com/whats-happening/).
|
|
||||||
|
|
||||||
Because the SDM and WWN APIs run on servers in the cloud, a connection with the Internet is required for sending and receiving information.
|
|
||||||
The binding uses HTTPS to connect to the APIs using port 443.
|
The binding uses HTTPS to connect to the APIs using port 443.
|
||||||
When using the WWN API, the binding also connects to servers on port 9553.
|
|
||||||
So make sure outbound connections to these ports are not blocked by a firewall.
|
So make sure outbound connections to these ports are not blocked by a firewall.
|
||||||
|
|
||||||
|
> Note: This binding no longer supports the Works with Nest (WWN) API because it has been discontinued by Google.
|
||||||
|
See [Support for Works with Nest ending](https://support.google.com/googlenest/answer/9293712).
|
||||||
|
|
||||||
## Supported Things
|
## Supported Things
|
||||||
|
|
||||||
The table below lists the Nest binding thing types:
|
The table below lists the Nest binding thing types:
|
||||||
|
|
||||||
| Things | Description | SDM Thing Type | WWN Thing Type |
|
| Things | Description | Thing Type |
|
||||||
|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|----------------|--------------------|
|
|-----------------------------------------|------------------------------------------------------------------------|----------------|
|
||||||
| Nest Account (SDM, WWN) | An account for using the Nest (SDM/WWN) REST API | sdm_account | wwn_account |
|
| Nest Account | An account for using the Nest SDM REST API | sdm_account |
|
||||||
| Nest Cam (Indoor, IQ, Outdoor), Dropcam | A Nest Cam registered with your account | sdm_camera | wwn_camera |
|
| Nest Cam (Indoor, IQ, Outdoor), Dropcam | A Nest Cam registered with your account | sdm_camera |
|
||||||
| Nest Hello Doorbell | A Nest Doorbell registered with your account | sdm_doorbell | wwn_camera |
|
| Nest Hello Doorbell | A Nest Doorbell registered with your account | sdm_doorbell |
|
||||||
| Nest Hub (Max) | A Nest Display registered with your account | sdm_display | wwn_camera |
|
| Nest Hub (Max) | A Nest Display registered with your account | sdm_display |
|
||||||
| Nest Protect | The smoke detector/Nest Protect for the account | | wwn_smoke_detector |
|
| Nest Thermostat (E) | A Thermostat to control the various aspects of the house's HVAC system | sdm_thermostat |
|
||||||
| Nest Thermostat (E) | A Thermostat to control the various aspects of the house's HVAC system | sdm_thermostat | wwn_thermostat |
|
|
||||||
| Structure | The Nest structure defines the house the account has setup on Nest. You will only have more than one structure if you have more than one house | | wwn_structure |
|
|
||||||
|
|
||||||
The SDM API currently does not support Nest Protect devices.
|
The SDM API currently does not support Nest Protect devices.
|
||||||
There are no structure Things when using the SDM API, because the SDM API does not support setting the Home/Away status like the WWN API does.
|
|
||||||
|
|
||||||
To use one of the Nest APIs, add the corresponding Account Thing using the UI and configure the required parameters.
|
To use the Nest SDM API, add an Account Thing using the UI and configure the required parameters.
|
||||||
After configuring an Account Thing, you can use it to discover the connected devices which are then added the Inbox.
|
After configuring an Account Thing, you can use it to discover the connected devices which are then added the Inbox.
|
||||||
|
|
||||||
## SDM Account Configuration
|
## Account Configuration
|
||||||
|
|
||||||
### Google Account Requirement
|
### Google Account Requirement
|
||||||
|
|
||||||
To be able to use the SDM API it is required that you use a Google Account with your Nest devices.
|
To be able to use the SDM API it is required that you use a Google Account with your Nest devices.
|
||||||
If you still use the WWN API, you can no longer use the WWN API after migrating to a Google Account.
|
|
||||||
So if you have not yet migrated your account, check that all the functionality you require is provided by the SDM API and SDM Things in the binding.
|
|
||||||
Most notably, there is no support for the Nest Protect in the SDM API and you cannot change your Home/Away status.
|
|
||||||
To migrate to a Google account, follow the migration steps in the [Nest accounts FAQ](https://support.google.com/googlenest/answer/9297676?co=GENIE.Platform%3DiOS&hl=en&oco=0#accountmigration&accountmigration1&#accountmigration2&#accountmigration3&zippy=%2Chow-do-i-migrate-my-account)
|
To migrate to a Google account, follow the migration steps in the [Nest accounts FAQ](https://support.google.com/googlenest/answer/9297676?co=GENIE.Platform%3DiOS&hl=en&oco=0#accountmigration&accountmigration1&#accountmigration2&#accountmigration3&zippy=%2Chow-do-i-migrate-my-account)
|
||||||
|
|
||||||
### SDM Configuration Parameters
|
### Configuration Parameters
|
||||||
|
|
||||||
These parameters configure which SDM project is accessed using the SDM API and configure the OAuth 2.0 client details used for accessing the project.
|
These parameters configure which SDM project is accessed using the SDM API and configure the OAuth 2.0 client details used for accessing the project.
|
||||||
|
|
||||||
|
@ -183,22 +173,17 @@ The created subscription can also be monitored using the Google Cloud Platform C
|
||||||
Decreasing the `refreshInterval` may cause issues when you have a lot of devices connected because it may cause API rate limits to be exceeded.
|
Decreasing the `refreshInterval` may cause issues when you have a lot of devices connected because it may cause API rate limits to be exceeded.
|
||||||
You may want to decrease the `refreshInterval` for a Thermostat if Pub/Sub events have not been configured to provide state updating.
|
You may want to decrease the `refreshInterval` for a Thermostat if Pub/Sub events have not been configured to provide state updating.
|
||||||
|
|
||||||
## WWN Account Configuration
|
|
||||||
|
|
||||||
To configure the binding to use the WWN API, add a new "Nest WWN Account" Thing in the UI and enter the **Product ID**, **Product Secret** and **Access Token** of an existing WWN account as configuration parameters.
|
|
||||||
It is no longer possible to register new WWN accounts with Nest because the WWN API runs in maintenance mode.
|
|
||||||
|
|
||||||
## Discovery
|
## Discovery
|
||||||
|
|
||||||
The binding will discover all Nest Things from your account when you add and configure a Nest SDM or WWN Account Thing.
|
The binding will discover all Nest Things from your account when you add and configure a Nest Account Thing.
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
### SDM/WWN Account Channels
|
### Account Channels
|
||||||
|
|
||||||
The account Thing Types do not have any channels.
|
The account Thing Types do not have any channels.
|
||||||
|
|
||||||
### SDM Camera/Display/Doorbell Channels
|
### Camera/Display/Doorbell Channels
|
||||||
|
|
||||||
The state of these channels is based on Pub/Sub events sent by the SDM API.
|
The state of these channels is based on Pub/Sub events sent by the SDM API.
|
||||||
So make sure the Pub/Sub account details are properly configured in the `sdm_account`.
|
So make sure the Pub/Sub account details are properly configured in the `sdm_account`.
|
||||||
|
@ -222,7 +207,7 @@ The `chime_event` group channels only exist for doorbell Things.
|
||||||
Each image channel has the `imageWidth` and `imageHeight` configuration parameters that can be used for configuring the image size in pixels.
|
Each image channel has the `imageWidth` and `imageHeight` configuration parameters that can be used for configuring the image size in pixels.
|
||||||
The maximum camera resolution is listed as `maxImageResolution` property in the Thing properties.
|
The maximum camera resolution is listed as `maxImageResolution` property in the Thing properties.
|
||||||
|
|
||||||
### SDM Thermostat Channels
|
### Thermostat Channels
|
||||||
|
|
||||||
| Channel Type ID | Item Type | Description | Read Write |
|
| Channel Type ID | Item Type | Description | Read Write |
|
||||||
|---------------------|----------------------|------------------------------------------------------------------------|:----------:|
|
|---------------------|----------------------|------------------------------------------------------------------------|:----------:|
|
||||||
|
@ -242,107 +227,6 @@ The maximum camera resolution is listed as `maxImageResolution` property in the
|
||||||
The `fan_timer_mode` channel has a `fanTimerDuration` configuration parameter that can be used for configuring how long the fan is ON before it is switched OFF (1s to 43200s).
|
The `fan_timer_mode` channel has a `fanTimerDuration` configuration parameter that can be used for configuring how long the fan is ON before it is switched OFF (1s to 43200s).
|
||||||
Similarly, when a DateTime command is sent to the `fan_timer_timeout` channel, the fan timer is switched ON and runs until the timestamp in the command (min now+1s, max now+43200s).
|
Similarly, when a DateTime command is sent to the `fan_timer_timeout` channel, the fan timer is switched ON and runs until the timestamp in the command (min now+1s, max now+43200s).
|
||||||
|
|
||||||
### WWN Camera Channels
|
|
||||||
|
|
||||||
#### Camera Group Channels
|
|
||||||
|
|
||||||
Information about the camera.
|
|
||||||
|
|
||||||
| Channel Type ID | Item Type | Description | Read Write |
|
|
||||||
|-----------------------|-----------|---------------------------------------------------|:----------:|
|
|
||||||
| app_url | String | The app URL to see the camera | R |
|
|
||||||
| audio_input_enabled | Switch | If the audio input is currently enabled | R |
|
|
||||||
| last_online_change | DateTime | Timestamp of the last online status change | R |
|
|
||||||
| public_share_enabled | Switch | If public sharing is currently enabled | R |
|
|
||||||
| public_share_url | String | The URL to see the public share of the camera | R |
|
|
||||||
| snapshot_url | String | The URL to use for a snapshot of the video stream | R |
|
|
||||||
| streaming | Switch | If the camera is currently streaming | R/W |
|
|
||||||
| video_history_enabled | Switch | If the video history is currently enabled | R |
|
|
||||||
| web_url | String | The web URL to see the camera | R |
|
|
||||||
|
|
||||||
#### Last Event Group Channels
|
|
||||||
|
|
||||||
Information about the last camera event (requires Nest Aware subscription).
|
|
||||||
|
|
||||||
| Channel Type ID | Item Type | Description | Read Write |
|
|
||||||
|--------------------|-----------|------------------------------------------------------------------------------------|:----------:|
|
|
||||||
| activity_zones | String | Identifiers for activity zones that detected the event (comma separated) | R |
|
|
||||||
| animated_image_url | String | The URL showing an animated image for the camera event | R |
|
|
||||||
| app_url | String | The app URL for the camera event, allows you to see the camera event in an app | R |
|
|
||||||
| end_time | DateTime | Timestamp when the camera event ended | R |
|
|
||||||
| has_motion | Switch | If motion was detected in the camera event | R |
|
|
||||||
| has_person | Switch | If a person was detected in the camera event | R |
|
|
||||||
| has_sound | Switch | If sound was detected in the camera event | R |
|
|
||||||
| image_url | String | The URL showing an image for the camera event | R |
|
|
||||||
| start_time | DateTime | Timestamp when the camera event started | R |
|
|
||||||
| urls_expire_time | DateTime | Timestamp when the camera event URLs expire | R |
|
|
||||||
| web_url | String | The web URL for the camera event, allows you to see the camera event in a web page | R |
|
|
||||||
|
|
||||||
### WWN Smoke Detector Channels
|
|
||||||
|
|
||||||
| Channel Type ID | Item Type | Description | Read Write |
|
|
||||||
|-----------------------|-----------|-----------------------------------------------------------------------------------|:----------:|
|
|
||||||
| co_alarm_state | String | The carbon monoxide alarm state of the Nest Protect (OK, EMERGENCY, WARNING) | R |
|
|
||||||
| last_connection | DateTime | Timestamp of the last successful interaction with Nest | R |
|
|
||||||
| last_manual_test_time | DateTime | Timestamp of the last successful manual test | R |
|
|
||||||
| low_battery | Switch | Reports whether the battery of the Nest protect is low (if it is battery powered) | R |
|
|
||||||
| manual_test_active | Switch | Manual test active at the moment | R |
|
|
||||||
| smoke_alarm_state | String | The smoke alarm state of the Nest Protect (OK, EMERGENCY, WARNING) | R |
|
|
||||||
| ui_color_state | String | The current color of the ring on the smoke detector (GRAY, GREEN, YELLOW, RED) | R |
|
|
||||||
|
|
||||||
### WWN Structure Channels
|
|
||||||
|
|
||||||
| Channel Type ID | Item Type | Description | Read Write |
|
|
||||||
|------------------------------|-----------|--------------------------------------------------------------------------------------------------------|:----------:|
|
|
||||||
| away | String | Away state of the structure (HOME, AWAY) | R/W |
|
|
||||||
| country_code | String | Country code of the structure ([ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)) | R |
|
|
||||||
| co_alarm_state | String | Carbon Monoxide alarm state (OK, EMERGENCY, WARNING) | R |
|
|
||||||
| eta_begin | DateTime | Estimated time of arrival at home, will setup the heat to turn on and be warm | R |
|
|
||||||
| peak_period_end_time | DateTime | Peak period end for the Rush Hour Rewards program | R |
|
|
||||||
| peak_period_start_time | DateTime | Peak period start for the Rush Hour Rewards program | R |
|
|
||||||
| postal_code | String | Postal code of the structure | R |
|
|
||||||
| rush_hour_rewards_enrollment | Switch | If rush hour rewards system is enabled or not | R |
|
|
||||||
| security_state | String | Security state of the structure (OK, DETER) | R |
|
|
||||||
| smoke_alarm_state | String | Smoke alarm state (OK, EMERGENCY, WARNING) | R |
|
|
||||||
| time_zone | String | The time zone for the structure ([IANA time zone format](https://www.iana.org/time-zones)) | R |
|
|
||||||
|
|
||||||
### WWN Thermostat Channels
|
|
||||||
|
|
||||||
| Channel Type ID | Item Type | Description | Read Write |
|
|
||||||
|-----------------------------|----------------------|----------------------------------------------------------------------------------------|:----------:|
|
|
||||||
| can_cool | Switch | If the thermostat can actually turn on cooling | R |
|
|
||||||
| can_heat | Switch | If the thermostat can actually turn on heating | R |
|
|
||||||
| eco_max_set_point | Number:Temperature | The eco range max set point temperature | R |
|
|
||||||
| eco_min_set_point | Number:Temperature | The eco range min set point temperature | R |
|
|
||||||
| fan_timer_active | Switch | If the fan timer is engaged | R/W |
|
|
||||||
| fan_timer_duration | Number:Time | Length of time that the fan is set to run (15, 30, 45, 60, 120, 240, 480, 960 minutes) | R/W |
|
|
||||||
| fan_timer_timeout | DateTime | Timestamp when the fan stops running | R |
|
|
||||||
| has_fan | Switch | If the thermostat can control the fan | R |
|
|
||||||
| has_leaf | Switch | If the thermostat is currently in a leaf mode | R |
|
|
||||||
| humidity | Number:Dimensionless | Indicates the current relative humidity | R |
|
|
||||||
| last_connection | DateTime | Timestamp of the last successful interaction with Nest | R |
|
|
||||||
| locked | Switch | If the thermostat has the temperature locked to only be within a set range | R |
|
|
||||||
| locked_max_set_point | Number:Temperature | The locked range max set point temperature | R |
|
|
||||||
| locked_min_set_point | Number:Temperature | The locked range min set point temperature | R |
|
|
||||||
| max_set_point | Number:Temperature | The max set point temperature | R/W |
|
|
||||||
| min_set_point | Number:Temperature | The min set point temperature | R/W |
|
|
||||||
| mode | String | Current mode of the Nest thermostat (HEAT, COOL, HEAT_COOL, ECO, OFF) | R/W |
|
|
||||||
| previous_mode | String | The previous mode of the Nest thermostat (HEAT, COOL, HEAT_COOL, ECO, OFF) | R |
|
|
||||||
| state | String | The active state of the Nest thermostat (HEATING, COOLING, OFF) | R |
|
|
||||||
| temperature | Number:Temperature | Current temperature | R |
|
|
||||||
| time_to_target | Number:Time | Time left to the target temperature approximately | R |
|
|
||||||
| set_point | Number:Temperature | The set point temperature | R/W |
|
|
||||||
| sunlight_correction_active | Switch | If sunlight correction is active | R |
|
|
||||||
| sunlight_correction_enabled | Switch | If sunlight correction is enabled | R |
|
|
||||||
| using_emergency_heat | Switch | If the system is currently using emergency heat | R |
|
|
||||||
|
|
||||||
Note that the Nest API rounds Thermostat values so they will differ from what shows up in the Nest App.
|
|
||||||
The Nest API applies the following rounding:
|
|
||||||
|
|
||||||
- degrees Celsius to 0.5 degrees
|
|
||||||
- degrees Fahrenheit to whole degrees
|
|
||||||
- humidity to 5%
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
You can use the discovery functionality of the binding to obtain the deviceId and structureId values for defining Nest things in files.
|
You can use the discovery functionality of the binding to obtain the deviceId and structureId values for defining Nest things in files.
|
||||||
|
@ -400,93 +284,3 @@ Number:Temperature Thermostat_Target_temperature "Target Temperature [%.1f %un
|
||||||
Number:Temperature Thermostat_Temperature_Cool "Temperature Cool [%.1f %unit%]" { channel="nest:sdm_thermostat:demo_sdm_account:living_thermostat:temperature_cool" }
|
Number:Temperature Thermostat_Temperature_Cool "Temperature Cool [%.1f %unit%]" { channel="nest:sdm_thermostat:demo_sdm_account:living_thermostat:temperature_cool" }
|
||||||
Number:Temperature Thermostat_Temperature_Heat "Temperature Heat [%.1f %unit%]" { channel="nest:sdm_thermostat:demo_sdm_account:living_thermostat:temperature_heat" }
|
Number:Temperature Thermostat_Temperature_Heat "Temperature Heat [%.1f %unit%]" { channel="nest:sdm_thermostat:demo_sdm_account:living_thermostat:temperature_heat" }
|
||||||
```
|
```
|
||||||
|
|
||||||
### wwn-demo.things
|
|
||||||
|
|
||||||
```java
|
|
||||||
Bridge nest:wwn_account:demo_wwn_account [ productId="8fdf9885-ca07-4252-1aa3-f3d5ca9589e0", productSecret="QITLR3iyUlWaj9dbvCxsCKp4f", accessToken="c.6rse1xtRk2UANErcY0XazaqPHgbvSSB6owOrbZrZ6IXrmqhsr9QTmcfaiLX1l0ULvlI5xLp01xmKeiojHqozLQbNM8yfITj1LSdK28zsUft1aKKH2mDlOeoqZKBdVIsxyZk4orH0AvKEZ5aY" ] {
|
|
||||||
Thing wwn_camera fish_cam [ deviceId="qw0NNE8ruxA9AGJkTaFH3KeUiJaONWKiH9Gh3RwwhHClonIexTtufQ" ]
|
|
||||||
Thing wwn_smoke_detector hallway_smoke [ deviceId="Tzvibaa3lLKnHpvpi9OQeCI_z5rfkBAV" ]
|
|
||||||
Thing wwn_structure home [ structureId="20wKjydArmMV3kOluTA7JRcZg8HKBzTR-G_2nRXuIN1Bd6laGLOJQw" ]
|
|
||||||
Thing wwn_thermostat living_thermostat [ deviceId="ZqAKzSv6TO6PjBnOCXf9LSI_z5rfkBAV" ]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### wwn-demo.items
|
|
||||||
|
|
||||||
```java
|
|
||||||
/* WWN Camera */
|
|
||||||
String Cam_App_URL "App URL [%s]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:camera#app_url" }
|
|
||||||
Switch Cam_Audio_Input_Enabled "Audio Input Enabled" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:camera#audio_input_enabled" }
|
|
||||||
DateTime Cam_Last_Online_Change "Last Online Change [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:camera#last_online_change" }
|
|
||||||
String Cam_Snapshot_URL "Snapshot URL [%s]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:camera#snapshot_url" }
|
|
||||||
Switch Cam_Streaming "Streaming" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:camera#streaming" }
|
|
||||||
Switch Cam_Public_Share_Enabled "Public Share Enabled" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:camera#public_share_enabled" }
|
|
||||||
String Cam_Public_Share_URL "Public Share URL [%s]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:camera#public_share_url" }
|
|
||||||
Switch Cam_Video_History_Enabled "Video History Enabled" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:camera#video_history_enabled" }
|
|
||||||
String Cam_Web_URL "Web URL [%s]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:camera#web_url" }
|
|
||||||
String Cam_LE_Activity_Zones "Last Event Activity Zones [%s]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#activity_zones" }
|
|
||||||
String Cam_LE_Animated_Image_URL "Last Event Animated Image URL [%s]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#animated_image_url" }
|
|
||||||
String Cam_LE_App_URL "Last Event App URL [%s]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#app_url" }
|
|
||||||
DateTime Cam_LE_End_Time "Last Event End Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#end_time" }
|
|
||||||
Switch Cam_LE_Has_Motion "Last Event Has Motion" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#has_motion" }
|
|
||||||
Switch Cam_LE_Has_Person "Last Event Has Person" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#has_person" }
|
|
||||||
Switch Cam_LE_Has_Sound "Last Event Has Sound" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#has_sound" }
|
|
||||||
String Cam_LE_Image_URL "Last Event Image URL [%s]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#image_url" }
|
|
||||||
DateTime Cam_LE_Start_Time "Last Event Start Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#start_time" }
|
|
||||||
DateTime Cam_LE_URLs_Expire_Time "Last Event URLs Expire Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#urls_expire_time" }
|
|
||||||
String Cam_LE_Web_URL "Last Event Web URL [%s]" { channel="nest:wwn_camera:demo_wwn_account:fish_cam:last_event#web_url" }
|
|
||||||
|
|
||||||
/* WWN Smoke Detector */
|
|
||||||
String Smoke_CO_Alarm "CO Alarm [%s]" { channel="nest:wwn_smoke_detector:demo_wwn_account:hallway_smoke:co_alarm_state" }
|
|
||||||
Switch Smoke_Battery_Low "Battery Low" { channel="nest:wwn_smoke_detector:demo_wwn_account:hallway_smoke:low_battery" }
|
|
||||||
Switch Smoke_Manual_Test "Manual Test" { channel="nest:wwn_smoke_detector:demo_wwn_account:hallway_smoke:manual_test_active" }
|
|
||||||
DateTime Smoke_Last_Connection "Last Connection [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_smoke_detector:demo_wwn_account:hallway_smoke:last_connection" }
|
|
||||||
DateTime Smoke_Last_Manual_Test "Last Manual Test [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_smoke_detector:demo_wwn_account:hallway_smoke:last_manual_test_time" }
|
|
||||||
String Smoke_Smoke_Alarm "Smoke Alarm [%s]" { channel="nest:wwn_smoke_detector:demo_wwn_account:hallway_smoke:smoke_alarm_state" }
|
|
||||||
String Smoke_UI_Color "UI Color [%s]" { channel="nest:wwn_smoke_detector:demo_wwn_account:hallway_smoke:ui_color_state" }
|
|
||||||
|
|
||||||
/* WWN Thermostat */
|
|
||||||
Switch Thermostat_Can_Cool "Can Cool" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:can_cool" }
|
|
||||||
Switch Thermostat_Can_Heat "Can Heat" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:can_heat" }
|
|
||||||
Number:Temperature Therm_EMaxSP "Eco Max Set Point [%.1f %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:eco_max_set_point" }
|
|
||||||
Number:Temperature Therm_EMinSP "Eco Min Set Point [%.1f %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:eco_min_set_point" }
|
|
||||||
Switch Thermostat_FT_Active "Fan Timer Active" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:fan_timer_active" }
|
|
||||||
Number:Time Thermostat_FT_Duration "Fan Timer Duration [%d %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:fan_timer_duration" }
|
|
||||||
DateTime Thermostat_FT_Timeout "Fan Timer Timeout [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:fan_timer_timeout" }
|
|
||||||
Switch Thermostat_Has_Fan "Has Fan" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:has_fan" }
|
|
||||||
Switch Thermostat_Has_Leaf "Has Leaf" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:has_leaf" }
|
|
||||||
Number:Dimensionless Therm_Hum "Humidity [%.1f %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:humidity" }
|
|
||||||
DateTime Thermostat_Last_Conn "Last Connection [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:last_connection" }
|
|
||||||
Switch Thermostat_Locked "Locked" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:locked" }
|
|
||||||
Number:Temperature Therm_LMaxSP "Locked Max Set Point [%.1f %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:locked_max_set_point" }
|
|
||||||
Number:Temperature Therm_LMinSP "Locked Min Set Point [%.1f %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:locked_min_set_point" }
|
|
||||||
Number:Temperature Therm_Max_SP "Max Set Point [%.1f %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:max_set_point" }
|
|
||||||
Number:Temperature Therm_Min_SP "Min Set Point [%.1f %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:min_set_point" }
|
|
||||||
String Thermostat_Mode "Mode [%s]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:mode" }
|
|
||||||
String Thermostat_Previous_Mode "Previous Mode [%s]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:previous_mode" }
|
|
||||||
String Thermostat_State "State [%s]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:state" }
|
|
||||||
Number:Temperature Thermostat_SP "Set Point [%.1f %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:set_point" }
|
|
||||||
Switch Thermostat_Sunlight_CA "Sunlight Correction Active" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:sunlight_correction_active" }
|
|
||||||
Switch Thermostat_Sunlight_CE "Sunlight Correction Enabled" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:sunlight_correction_enabled" }
|
|
||||||
Number:Temperature Therm_Temp "Temperature [%.1f %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:temperature" }
|
|
||||||
Number:Time Therm_Time_To_Target "Time To Target [%d %unit%]" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:time_to_target" }
|
|
||||||
Switch Thermostat_Using_Em_Heat "Using Emergency Heat" { channel="nest:wwn_thermostat:demo_wwn_account:living_thermostat:using_emergency_heat" }
|
|
||||||
|
|
||||||
/* WWN Structure */
|
|
||||||
String Home_Away "Away [%s]" { channel="nest:wwn_structure:demo_wwn_account:home:away" }
|
|
||||||
String Home_Country_Code "Country Code [%s]" { channel="nest:wwn_structure:demo_wwn_account:home:country_code" }
|
|
||||||
String Home_CO_Alarm_State "CO Alarm State [%s]" { channel="nest:wwn_structure:demo_wwn_account:home:co_alarm_state" }
|
|
||||||
DateTime Home_ETA "ETA [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_structure:demo_wwn_account:home:eta_begin" }
|
|
||||||
DateTime Home_PP_End_Time "PP End Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_structure:demo_wwn_account:home:peak_period_end_time" }
|
|
||||||
DateTime Home_PP_Start_Time "PP Start Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" { channel="nest:wwn_structure:demo_wwn_account:home:peak_period_start_time" }
|
|
||||||
String Home_Postal_Code "Postal Code [%s]" { channel="nest:wwn_structure:demo_wwn_account:home:postal_code" }
|
|
||||||
Switch Home_Rush_Hour_Rewards "Rush Hour Rewards" { channel="nest:wwn_structure:demo_wwn_account:home:rush_hour_rewards_enrollment" }
|
|
||||||
String Home_Security_State "Security State [%s]" { channel="nest:wwn_structure:demo_wwn_account:home:security_state" }
|
|
||||||
String Home_Smoke_Alarm_State "Smoke Alarm State [%s]" { channel="nest:wwn_structure:demo_wwn_account:home:smoke_alarm_state" }
|
|
||||||
String Home_Time_Zone "Time Zone [%s]" { channel="nest:wwn_structure:demo_wwn_account:home:time_zone" }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This documentation contains parts written by John Cocula which were copied from the 1.0 Nest binding.
|
|
||||||
|
|
|
@ -1,152 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link WWNBindingConstants} class defines common constants which are used for the WWN implementation in the
|
|
||||||
* binding.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNBindingConstants {
|
|
||||||
|
|
||||||
public static final String BINDING_ID = "nest";
|
|
||||||
|
|
||||||
/** The URL to use to connect to Nest with. */
|
|
||||||
public static final String NEST_URL = "https://developer-api.nest.com";
|
|
||||||
|
|
||||||
/** The URL to get the access token when talking to Nest. */
|
|
||||||
public static final String NEST_ACCESS_TOKEN_URL = "https://api.home.nest.com/oauth2/access_token";
|
|
||||||
|
|
||||||
/** The path to set values on the thermostat when talking to Nest. */
|
|
||||||
public static final String NEST_THERMOSTAT_UPDATE_PATH = "/devices/thermostats/";
|
|
||||||
|
|
||||||
/** The path to set values on the structure when talking to Nest. */
|
|
||||||
public static final String NEST_STRUCTURE_UPDATE_PATH = "/structures/";
|
|
||||||
|
|
||||||
/** The path to set values on the camera when talking to Nest. */
|
|
||||||
public static final String NEST_CAMERA_UPDATE_PATH = "/devices/cameras/";
|
|
||||||
|
|
||||||
/** The path to set values on the camera when talking to Nest. */
|
|
||||||
public static final String NEST_SMOKE_ALARM_UPDATE_PATH = "/devices/smoke_co_alarms/";
|
|
||||||
|
|
||||||
/** The JSON content type used when talking to Nest. */
|
|
||||||
public static final String JSON_CONTENT_TYPE = "application/json";
|
|
||||||
|
|
||||||
/** To keep the streaming REST connection alive Nest sends every 30 seconds a message. */
|
|
||||||
public static final long KEEP_ALIVE_MILLIS = Duration.ofSeconds(30).toMillis();
|
|
||||||
|
|
||||||
/** To avoid API throttling errors (429 Too Many Requests) Nest recommends making at most one call per minute. */
|
|
||||||
public static final int MIN_SECONDS_BETWEEN_API_CALLS = 60;
|
|
||||||
|
|
||||||
// List of all Thing Type UIDs
|
|
||||||
public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "wwn_account");
|
|
||||||
public static final ThingTypeUID THING_TYPE_CAMERA = new ThingTypeUID(BINDING_ID, "wwn_camera");
|
|
||||||
public static final ThingTypeUID THING_TYPE_SMOKE_DETECTOR = new ThingTypeUID(BINDING_ID, "wwn_smoke_detector");
|
|
||||||
public static final ThingTypeUID THING_TYPE_STRUCTURE = new ThingTypeUID(BINDING_ID, "wwn_structure");
|
|
||||||
public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "wwn_thermostat");
|
|
||||||
|
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_CAMERA,
|
|
||||||
THING_TYPE_SMOKE_DETECTOR, THING_TYPE_STRUCTURE, THING_TYPE_THERMOSTAT);
|
|
||||||
|
|
||||||
// List of all channel group prefixes
|
|
||||||
public static final String CHANNEL_GROUP_CAMERA_PREFIX = "camera#";
|
|
||||||
public static final String CHANNEL_GROUP_LAST_EVENT_PREFIX = "last_event#";
|
|
||||||
|
|
||||||
// List of all Channel IDs
|
|
||||||
// read only channels (common)
|
|
||||||
public static final String CHANNEL_LAST_CONNECTION = "last_connection";
|
|
||||||
|
|
||||||
// read/write channels (thermostat)
|
|
||||||
public static final String CHANNEL_MODE = "mode";
|
|
||||||
public static final String CHANNEL_SET_POINT = "set_point";
|
|
||||||
public static final String CHANNEL_MAX_SET_POINT = "max_set_point";
|
|
||||||
public static final String CHANNEL_MIN_SET_POINT = "min_set_point";
|
|
||||||
public static final String CHANNEL_FAN_TIMER_ACTIVE = "fan_timer_active";
|
|
||||||
public static final String CHANNEL_FAN_TIMER_DURATION = "fan_timer_duration";
|
|
||||||
|
|
||||||
// read only channels (thermostat)
|
|
||||||
public static final String CHANNEL_ECO_MAX_SET_POINT = "eco_max_set_point";
|
|
||||||
public static final String CHANNEL_ECO_MIN_SET_POINT = "eco_min_set_point";
|
|
||||||
public static final String CHANNEL_LOCKED = "locked";
|
|
||||||
public static final String CHANNEL_LOCKED_MAX_SET_POINT = "locked_max_set_point";
|
|
||||||
public static final String CHANNEL_LOCKED_MIN_SET_POINT = "locked_min_set_point";
|
|
||||||
public static final String CHANNEL_TEMPERATURE = "temperature";
|
|
||||||
public static final String CHANNEL_HUMIDITY = "humidity";
|
|
||||||
public static final String CHANNEL_PREVIOUS_MODE = "previous_mode";
|
|
||||||
public static final String CHANNEL_STATE = "state";
|
|
||||||
public static final String CHANNEL_CAN_HEAT = "can_heat";
|
|
||||||
public static final String CHANNEL_CAN_COOL = "can_cool";
|
|
||||||
public static final String CHANNEL_FAN_TIMER_TIMEOUT = "fan_timer_timeout";
|
|
||||||
public static final String CHANNEL_HAS_FAN = "has_fan";
|
|
||||||
public static final String CHANNEL_HAS_LEAF = "has_leaf";
|
|
||||||
public static final String CHANNEL_SUNLIGHT_CORRECTION_ENABLED = "sunlight_correction_enabled";
|
|
||||||
public static final String CHANNEL_SUNLIGHT_CORRECTION_ACTIVE = "sunlight_correction_active";
|
|
||||||
public static final String CHANNEL_TIME_TO_TARGET = "time_to_target";
|
|
||||||
public static final String CHANNEL_USING_EMERGENCY_HEAT = "using_emergency_heat";
|
|
||||||
|
|
||||||
// read/write channels (camera)
|
|
||||||
public static final String CHANNEL_CAMERA_STREAMING = "camera#streaming";
|
|
||||||
|
|
||||||
// read only channels (camera)
|
|
||||||
public static final String CHANNEL_CAMERA_AUDIO_INPUT_ENABLED = "camera#audio_input_enabled";
|
|
||||||
public static final String CHANNEL_CAMERA_VIDEO_HISTORY_ENABLED = "camera#video_history_enabled";
|
|
||||||
public static final String CHANNEL_CAMERA_WEB_URL = "camera#web_url";
|
|
||||||
public static final String CHANNEL_CAMERA_APP_URL = "camera#app_url";
|
|
||||||
public static final String CHANNEL_CAMERA_PUBLIC_SHARE_ENABLED = "camera#public_share_enabled";
|
|
||||||
public static final String CHANNEL_CAMERA_PUBLIC_SHARE_URL = "camera#public_share_url";
|
|
||||||
public static final String CHANNEL_CAMERA_SNAPSHOT_URL = "camera#snapshot_url";
|
|
||||||
public static final String CHANNEL_CAMERA_LAST_ONLINE_CHANGE = "camera#last_online_change";
|
|
||||||
|
|
||||||
public static final String CHANNEL_LAST_EVENT_HAS_SOUND = "last_event#has_sound";
|
|
||||||
public static final String CHANNEL_LAST_EVENT_HAS_MOTION = "last_event#has_motion";
|
|
||||||
public static final String CHANNEL_LAST_EVENT_HAS_PERSON = "last_event#has_person";
|
|
||||||
public static final String CHANNEL_LAST_EVENT_START_TIME = "last_event#start_time";
|
|
||||||
public static final String CHANNEL_LAST_EVENT_END_TIME = "last_event#end_time";
|
|
||||||
public static final String CHANNEL_LAST_EVENT_URLS_EXPIRE_TIME = "last_event#urls_expire_time";
|
|
||||||
public static final String CHANNEL_LAST_EVENT_WEB_URL = "last_event#web_url";
|
|
||||||
public static final String CHANNEL_LAST_EVENT_APP_URL = "last_event#app_url";
|
|
||||||
public static final String CHANNEL_LAST_EVENT_IMAGE_URL = "last_event#image_url";
|
|
||||||
public static final String CHANNEL_LAST_EVENT_ANIMATED_IMAGE_URL = "last_event#animated_image_url";
|
|
||||||
public static final String CHANNEL_LAST_EVENT_ACTIVITY_ZONES = "last_event#activity_zones";
|
|
||||||
|
|
||||||
// read/write channels (smoke detector)
|
|
||||||
|
|
||||||
// read only channels (smoke detector)
|
|
||||||
public static final String CHANNEL_UI_COLOR_STATE = "ui_color_state";
|
|
||||||
public static final String CHANNEL_LOW_BATTERY = "low_battery";
|
|
||||||
public static final String CHANNEL_CO_ALARM_STATE = "co_alarm_state"; // Also in structure
|
|
||||||
public static final String CHANNEL_SMOKE_ALARM_STATE = "smoke_alarm_state"; // Also in structure
|
|
||||||
public static final String CHANNEL_MANUAL_TEST_ACTIVE = "manual_test_active";
|
|
||||||
public static final String CHANNEL_LAST_MANUAL_TEST_TIME = "last_manual_test_time";
|
|
||||||
|
|
||||||
// read/write channel (structure)
|
|
||||||
public static final String CHANNEL_AWAY = "away";
|
|
||||||
|
|
||||||
// read only channels (structure)
|
|
||||||
public static final String CHANNEL_COUNTRY_CODE = "country_code";
|
|
||||||
public static final String CHANNEL_POSTAL_CODE = "postal_code";
|
|
||||||
public static final String CHANNEL_PEAK_PERIOD_START_TIME = "peak_period_start_time";
|
|
||||||
public static final String CHANNEL_PEAK_PERIOD_END_TIME = "peak_period_end_time";
|
|
||||||
public static final String CHANNEL_TIME_ZONE = "time_zone";
|
|
||||||
public static final String CHANNEL_ETA_BEGIN = "eta_begin";
|
|
||||||
public static final String CHANNEL_RUSH_HOUR_REWARDS_ENROLLMENT = "rush_hour_rewards_enrollment";
|
|
||||||
public static final String CHANNEL_SECURITY_STATE = "security_state";
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn;
|
|
||||||
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.handler.WWNAccountHandler;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.handler.WWNCameraHandler;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.handler.WWNSmokeDetectorHandler;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.handler.WWNStructureHandler;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.handler.WWNThermostatHandler;
|
|
||||||
import org.openhab.core.thing.Bridge;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
|
||||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
|
||||||
import org.openhab.core.thing.binding.ThingHandler;
|
|
||||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
|
||||||
import org.osgi.service.component.annotations.Activate;
|
|
||||||
import org.osgi.service.component.annotations.Component;
|
|
||||||
import org.osgi.service.component.annotations.Reference;
|
|
||||||
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link WWNThingHandlerFactory} is responsible for creating WWN thing handlers.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.nest")
|
|
||||||
public class WWNThingHandlerFactory extends BaseThingHandlerFactory {
|
|
||||||
|
|
||||||
private final ClientBuilder clientBuilder;
|
|
||||||
private final SseEventSourceFactory eventSourceFactory;
|
|
||||||
|
|
||||||
@Activate
|
|
||||||
public WWNThingHandlerFactory(@Reference ClientBuilder clientBuilder,
|
|
||||||
@Reference SseEventSourceFactory eventSourceFactory) {
|
|
||||||
this.clientBuilder = clientBuilder;
|
|
||||||
this.eventSourceFactory = eventSourceFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The things this factory supports creating.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
|
||||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a handler for the specific thing. This also creates the discovery service when the bridge is created.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
|
||||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
|
||||||
|
|
||||||
if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) {
|
|
||||||
return new WWNAccountHandler((Bridge) thing, clientBuilder, eventSourceFactory);
|
|
||||||
} else if (THING_TYPE_CAMERA.equals(thingTypeUID)) {
|
|
||||||
return new WWNCameraHandler(thing);
|
|
||||||
} else if (THING_TYPE_SMOKE_DETECTOR.equals(thingTypeUID)) {
|
|
||||||
return new WWNSmokeDetectorHandler(thing);
|
|
||||||
} else if (THING_TYPE_STRUCTURE.equals(thingTypeUID)) {
|
|
||||||
return new WWNStructureHandler(thing);
|
|
||||||
} else if (THING_TYPE_THERMOSTAT.equals(thingTypeUID)) {
|
|
||||||
return new WWNThermostatHandler(thing);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn;
|
|
||||||
|
|
||||||
import java.io.Reader;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
|
|
||||||
import com.google.gson.FieldNamingPolicy;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for sharing WWN utility methods between objects.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public final class WWNUtils {
|
|
||||||
|
|
||||||
private static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
|
||||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
|
||||||
|
|
||||||
private WWNUtils() {
|
|
||||||
// hidden utility class constructor
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T fromJson(String json, Class<T> dataClass) {
|
|
||||||
return GSON.fromJson(json, dataClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T fromJson(Reader reader, Class<T> dataClass) {
|
|
||||||
return GSON.fromJson(reader, dataClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toJson(Object object) {
|
|
||||||
return GSON.toJson(object);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.config;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The configuration for the WWN account, allowing it to talk to Nest.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNAccountConfiguration {
|
|
||||||
public static final String PRODUCT_ID = "productId";
|
|
||||||
/** Product ID from the Nest product page. */
|
|
||||||
public String productId = "";
|
|
||||||
|
|
||||||
public static final String PRODUCT_SECRET = "productSecret";
|
|
||||||
/** Product secret from the Nest product page. */
|
|
||||||
public String productSecret = "";
|
|
||||||
|
|
||||||
public static final String PINCODE = "pincode";
|
|
||||||
/** Product pincode from the Nest authorization page. */
|
|
||||||
public @Nullable String pincode;
|
|
||||||
|
|
||||||
public static final String ACCESS_TOKEN = "accessToken";
|
|
||||||
/** The access token to use once retrieved from Nest. */
|
|
||||||
public @Nullable String accessToken;
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.config;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The configuration for WWN devices.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Add device configuration to allow file based configuration
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNDeviceConfiguration {
|
|
||||||
public static final String DEVICE_ID = "deviceId";
|
|
||||||
/** Device ID which can be retrieved with the Nest API. */
|
|
||||||
public String deviceId = "";
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.config;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The configuration for WWN structures.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Add device configuration to allow file based configuration
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNStructureConfiguration {
|
|
||||||
public static final String STRUCTURE_ID = "structureId";
|
|
||||||
/** Structure ID which can be retrieved with the Nest API. */
|
|
||||||
public String structureId = "";
|
|
||||||
}
|
|
|
@ -1,176 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.discovery;
|
|
||||||
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNStructureConfiguration;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.BaseWWNDevice;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNCamera;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNSmokeDetector;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNStructure;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNThermostat;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.handler.WWNAccountHandler;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.listener.WWNThingDataListener;
|
|
||||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
|
||||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This service connects to the Nest account and creates the correct discovery results for devices
|
|
||||||
* as they are found through the WWN API.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Add representation properties
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
|
|
||||||
|
|
||||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_CAMERA, THING_TYPE_THERMOSTAT,
|
|
||||||
THING_TYPE_SMOKE_DETECTOR, THING_TYPE_STRUCTURE);
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNDiscoveryService.class);
|
|
||||||
|
|
||||||
private final DiscoveryDataListener<WWNCamera> cameraDiscoveryDataListener = new DiscoveryDataListener<>(
|
|
||||||
WWNCamera.class, THING_TYPE_CAMERA, this::addDeviceDiscoveryResult);
|
|
||||||
private final DiscoveryDataListener<WWNSmokeDetector> smokeDetectorDiscoveryDataListener = new DiscoveryDataListener<>(
|
|
||||||
WWNSmokeDetector.class, THING_TYPE_SMOKE_DETECTOR, this::addDeviceDiscoveryResult);
|
|
||||||
private final DiscoveryDataListener<WWNStructure> structureDiscoveryDataListener = new DiscoveryDataListener<>(
|
|
||||||
WWNStructure.class, THING_TYPE_STRUCTURE, this::addStructureDiscoveryResult);
|
|
||||||
private final DiscoveryDataListener<WWNThermostat> thermostatDiscoveryDataListener = new DiscoveryDataListener<>(
|
|
||||||
WWNThermostat.class, THING_TYPE_THERMOSTAT, this::addDeviceDiscoveryResult);
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private final List<DiscoveryDataListener> discoveryDataListeners = List.of(cameraDiscoveryDataListener,
|
|
||||||
smokeDetectorDiscoveryDataListener, structureDiscoveryDataListener, thermostatDiscoveryDataListener);
|
|
||||||
|
|
||||||
private @NonNullByDefault({}) WWNAccountHandler accountHandler;
|
|
||||||
|
|
||||||
private static class DiscoveryDataListener<T> implements WWNThingDataListener<T> {
|
|
||||||
private Class<T> dataClass;
|
|
||||||
private ThingTypeUID thingTypeUID;
|
|
||||||
private BiConsumer<T, ThingTypeUID> onDiscovered;
|
|
||||||
|
|
||||||
private DiscoveryDataListener(Class<T> dataClass, ThingTypeUID thingTypeUID,
|
|
||||||
BiConsumer<T, ThingTypeUID> onDiscovered) {
|
|
||||||
this.dataClass = dataClass;
|
|
||||||
this.thingTypeUID = thingTypeUID;
|
|
||||||
this.onDiscovered = onDiscovered;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNewData(T data) {
|
|
||||||
onDiscovered.accept(data, thingTypeUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpdatedData(T oldData, T data) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMissingData(String nestId) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public WWNDiscoveryService() {
|
|
||||||
super(SUPPORTED_THING_TYPES, 60, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void activate() {
|
|
||||||
discoveryDataListeners.forEach(listener -> accountHandler.addThingDataListener(listener.dataClass, listener));
|
|
||||||
addDiscoveryResultsFromLastUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void deactivate() {
|
|
||||||
discoveryDataListeners
|
|
||||||
.forEach(listener -> accountHandler.removeThingDataListener(listener.dataClass, listener));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable ThingHandler getThingHandler() {
|
|
||||||
return accountHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setThingHandler(ThingHandler handler) {
|
|
||||||
if (handler instanceof WWNAccountHandler) {
|
|
||||||
accountHandler = (WWNAccountHandler) handler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void startScan() {
|
|
||||||
addDiscoveryResultsFromLastUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void addDiscoveryResultsFromLastUpdates() {
|
|
||||||
discoveryDataListeners.forEach(listener -> addDiscoveryResultsFromLastUpdates(listener.dataClass,
|
|
||||||
listener.thingTypeUID, listener.onDiscovered));
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> void addDiscoveryResultsFromLastUpdates(Class<T> dataClass, ThingTypeUID thingTypeUID,
|
|
||||||
BiConsumer<T, ThingTypeUID> onDiscovered) {
|
|
||||||
List<T> lastUpdates = accountHandler.getLastUpdates(dataClass);
|
|
||||||
lastUpdates.forEach(lastUpdate -> onDiscovered.accept(lastUpdate, thingTypeUID));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addDeviceDiscoveryResult(BaseWWNDevice device, ThingTypeUID typeUID) {
|
|
||||||
ThingUID bridgeUID = accountHandler.getThing().getUID();
|
|
||||||
ThingUID thingUID = new ThingUID(typeUID, bridgeUID, device.getDeviceId());
|
|
||||||
logger.debug("Discovered {}", thingUID);
|
|
||||||
Map<String, Object> properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, device.getDeviceId(),
|
|
||||||
PROPERTY_FIRMWARE_VERSION, device.getSoftwareVersion());
|
|
||||||
thingDiscovered(DiscoveryResultBuilder.create(thingUID) //
|
|
||||||
.withThingType(typeUID) //
|
|
||||||
.withLabel(device.getNameLong()) //
|
|
||||||
.withBridge(bridgeUID) //
|
|
||||||
.withProperties(properties) //
|
|
||||||
.withRepresentationProperty(WWNDeviceConfiguration.DEVICE_ID) //
|
|
||||||
.build() //
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addStructureDiscoveryResult(WWNStructure structure, ThingTypeUID typeUID) {
|
|
||||||
ThingUID bridgeUID = accountHandler.getThing().getUID();
|
|
||||||
ThingUID thingUID = new ThingUID(typeUID, bridgeUID, structure.getStructureId());
|
|
||||||
logger.debug("Discovered {}", thingUID);
|
|
||||||
Map<String, Object> properties = Map.of(WWNStructureConfiguration.STRUCTURE_ID, structure.getStructureId());
|
|
||||||
thingDiscovered(DiscoveryResultBuilder.create(thingUID) //
|
|
||||||
.withThingType(THING_TYPE_STRUCTURE) //
|
|
||||||
.withLabel(structure.getName()) //
|
|
||||||
.withBridge(bridgeUID) //
|
|
||||||
.withProperties(properties) //
|
|
||||||
.withRepresentationProperty(WWNStructureConfiguration.STRUCTURE_ID) //
|
|
||||||
.build() //
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,167 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default properties shared across all WWN devices.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Add equals and hashCode methods
|
|
||||||
*/
|
|
||||||
public class BaseWWNDevice implements WWNIdentifiable {
|
|
||||||
|
|
||||||
private String deviceId;
|
|
||||||
private String name;
|
|
||||||
private String nameLong;
|
|
||||||
private Date lastConnection;
|
|
||||||
private Boolean isOnline;
|
|
||||||
private String softwareVersion;
|
|
||||||
private String structureId;
|
|
||||||
|
|
||||||
private String whereId;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDeviceId() {
|
|
||||||
return deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getLastConnection() {
|
|
||||||
return lastConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isOnline() {
|
|
||||||
return isOnline;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNameLong() {
|
|
||||||
return nameLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSoftwareVersion() {
|
|
||||||
return softwareVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStructureId() {
|
|
||||||
return structureId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getWhereId() {
|
|
||||||
return whereId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
BaseWWNDevice other = (BaseWWNDevice) obj;
|
|
||||||
if (deviceId == null) {
|
|
||||||
if (other.deviceId != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!deviceId.equals(other.deviceId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isOnline == null) {
|
|
||||||
if (other.isOnline != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!isOnline.equals(other.isOnline)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lastConnection == null) {
|
|
||||||
if (other.lastConnection != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!lastConnection.equals(other.lastConnection)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (name == null) {
|
|
||||||
if (other.name != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!name.equals(other.name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (nameLong == null) {
|
|
||||||
if (other.nameLong != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!nameLong.equals(other.nameLong)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (softwareVersion == null) {
|
|
||||||
if (other.softwareVersion != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!softwareVersion.equals(other.softwareVersion)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (structureId == null) {
|
|
||||||
if (other.structureId != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!structureId.equals(other.structureId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (whereId == null) {
|
|
||||||
if (other.whereId != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!whereId.equals(other.whereId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((deviceId == null) ? 0 : deviceId.hashCode());
|
|
||||||
result = prime * result + ((isOnline == null) ? 0 : isOnline.hashCode());
|
|
||||||
result = prime * result + ((lastConnection == null) ? 0 : lastConnection.hashCode());
|
|
||||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
|
||||||
result = prime * result + ((nameLong == null) ? 0 : nameLong.hashCode());
|
|
||||||
result = prime * result + ((softwareVersion == null) ? 0 : softwareVersion.hashCode());
|
|
||||||
result = prime * result + ((structureId == null) ? 0 : structureId.hashCode());
|
|
||||||
result = prime * result + ((whereId == null) ? 0 : whereId.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("BaseNestDevice [deviceId=").append(deviceId).append(", name=").append(name)
|
|
||||||
.append(", nameLong=").append(nameLong).append(", lastConnection=").append(lastConnection)
|
|
||||||
.append(", isOnline=").append(isOnline).append(", softwareVersion=").append(softwareVersion)
|
|
||||||
.append(", structureId=").append(structureId).append(", whereId=").append(whereId).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deals with the access token data that comes back from WWN when it is requested.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Add equals and hashCode methods
|
|
||||||
*/
|
|
||||||
public class WWNAccessTokenData {
|
|
||||||
|
|
||||||
private String accessToken;
|
|
||||||
private Long expiresIn;
|
|
||||||
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getExpiresIn() {
|
|
||||||
return expiresIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNAccessTokenData other = (WWNAccessTokenData) obj;
|
|
||||||
if (accessToken == null) {
|
|
||||||
if (other.accessToken != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!accessToken.equals(other.accessToken)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (expiresIn == null) {
|
|
||||||
if (other.expiresIn != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!expiresIn.equals(other.expiresIn)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((accessToken == null) ? 0 : accessToken.hashCode());
|
|
||||||
result = prime * result + ((expiresIn == null) ? 0 : expiresIn.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("AccessTokenData [accessToken=").append(accessToken).append(", expiresIn=").append(expiresIn)
|
|
||||||
.append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The data for a WWN camera activity zone.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Extract ActivityZone object from Camera
|
|
||||||
*/
|
|
||||||
public class WWNActivityZone {
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
private int id;
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNActivityZone other = (WWNActivityZone) obj;
|
|
||||||
if (id != other.id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (name == null) {
|
|
||||||
if (other.name != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!name.equals(other.name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + id;
|
|
||||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("CameraActivityZone [name=").append(name).append(", id=").append(id).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,209 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The data for the WWN camera.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Add equals and hashCode methods
|
|
||||||
*/
|
|
||||||
public class WWNCamera extends BaseWWNDevice {
|
|
||||||
|
|
||||||
private Boolean isStreaming;
|
|
||||||
private Boolean isAudioInputEnabled;
|
|
||||||
private Date lastIsOnlineChange;
|
|
||||||
private Boolean isVideoHistoryEnabled;
|
|
||||||
private String webUrl;
|
|
||||||
private String appUrl;
|
|
||||||
private Boolean isPublicShareEnabled;
|
|
||||||
private List<WWNActivityZone> activityZones;
|
|
||||||
private String publicShareUrl;
|
|
||||||
private String snapshotUrl;
|
|
||||||
private WWNCameraEvent lastEvent;
|
|
||||||
|
|
||||||
public Boolean isStreaming() {
|
|
||||||
return isStreaming;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isAudioInputEnabled() {
|
|
||||||
return isAudioInputEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getLastIsOnlineChange() {
|
|
||||||
return lastIsOnlineChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isVideoHistoryEnabled() {
|
|
||||||
return isVideoHistoryEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getWebUrl() {
|
|
||||||
return webUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAppUrl() {
|
|
||||||
return appUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isPublicShareEnabled() {
|
|
||||||
return isPublicShareEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<WWNActivityZone> getActivityZones() {
|
|
||||||
return activityZones;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPublicShareUrl() {
|
|
||||||
return publicShareUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSnapshotUrl() {
|
|
||||||
return snapshotUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WWNCameraEvent getLastEvent() {
|
|
||||||
return lastEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || !super.equals(obj)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNCamera other = (WWNCamera) obj;
|
|
||||||
if (activityZones == null) {
|
|
||||||
if (other.activityZones != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!activityZones.equals(other.activityZones)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (appUrl == null) {
|
|
||||||
if (other.appUrl != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!appUrl.equals(other.appUrl)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isAudioInputEnabled == null) {
|
|
||||||
if (other.isAudioInputEnabled != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!isAudioInputEnabled.equals(other.isAudioInputEnabled)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isPublicShareEnabled == null) {
|
|
||||||
if (other.isPublicShareEnabled != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!isPublicShareEnabled.equals(other.isPublicShareEnabled)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isStreaming == null) {
|
|
||||||
if (other.isStreaming != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!isStreaming.equals(other.isStreaming)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isVideoHistoryEnabled == null) {
|
|
||||||
if (other.isVideoHistoryEnabled != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!isVideoHistoryEnabled.equals(other.isVideoHistoryEnabled)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lastEvent == null) {
|
|
||||||
if (other.lastEvent != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!lastEvent.equals(other.lastEvent)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lastIsOnlineChange == null) {
|
|
||||||
if (other.lastIsOnlineChange != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!lastIsOnlineChange.equals(other.lastIsOnlineChange)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (publicShareUrl == null) {
|
|
||||||
if (other.publicShareUrl != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!publicShareUrl.equals(other.publicShareUrl)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (snapshotUrl == null) {
|
|
||||||
if (other.snapshotUrl != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!snapshotUrl.equals(other.snapshotUrl)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (webUrl == null) {
|
|
||||||
if (other.webUrl != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!webUrl.equals(other.webUrl)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = super.hashCode();
|
|
||||||
result = prime * result + ((activityZones == null) ? 0 : activityZones.hashCode());
|
|
||||||
result = prime * result + ((appUrl == null) ? 0 : appUrl.hashCode());
|
|
||||||
result = prime * result + ((isAudioInputEnabled == null) ? 0 : isAudioInputEnabled.hashCode());
|
|
||||||
result = prime * result + ((isPublicShareEnabled == null) ? 0 : isPublicShareEnabled.hashCode());
|
|
||||||
result = prime * result + ((isStreaming == null) ? 0 : isStreaming.hashCode());
|
|
||||||
result = prime * result + ((isVideoHistoryEnabled == null) ? 0 : isVideoHistoryEnabled.hashCode());
|
|
||||||
result = prime * result + ((lastEvent == null) ? 0 : lastEvent.hashCode());
|
|
||||||
result = prime * result + ((lastIsOnlineChange == null) ? 0 : lastIsOnlineChange.hashCode());
|
|
||||||
result = prime * result + ((publicShareUrl == null) ? 0 : publicShareUrl.hashCode());
|
|
||||||
result = prime * result + ((snapshotUrl == null) ? 0 : snapshotUrl.hashCode());
|
|
||||||
result = prime * result + ((webUrl == null) ? 0 : webUrl.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("Camera [isStreaming=").append(isStreaming).append(", isAudioInputEnabled=")
|
|
||||||
.append(isAudioInputEnabled).append(", lastIsOnlineChange=").append(lastIsOnlineChange)
|
|
||||||
.append(", isVideoHistoryEnabled=").append(isVideoHistoryEnabled).append(", webUrl=").append(webUrl)
|
|
||||||
.append(", appUrl=").append(appUrl).append(", isPublicShareEnabled=").append(isPublicShareEnabled)
|
|
||||||
.append(", activityZones=").append(activityZones).append(", publicShareUrl=").append(publicShareUrl)
|
|
||||||
.append(", snapshotUrl=").append(snapshotUrl).append(", lastEvent=").append(lastEvent)
|
|
||||||
.append(", getId()=").append(getId()).append(", getName()=").append(getName())
|
|
||||||
.append(", getDeviceId()=").append(getDeviceId()).append(", getLastConnection()=")
|
|
||||||
.append(getLastConnection()).append(", isOnline()=").append(isOnline()).append(", getNameLong()=")
|
|
||||||
.append(getNameLong()).append(", getSoftwareVersion()=").append(getSoftwareVersion())
|
|
||||||
.append(", getStructureId()=").append(getStructureId()).append(", getWhereId()=").append(getWhereId())
|
|
||||||
.append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,203 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The data for a WWN camera event.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Extract CameraEvent object from Camera
|
|
||||||
* @author Wouter Born - Add equals, hashCode, toString methods
|
|
||||||
*/
|
|
||||||
public class WWNCameraEvent {
|
|
||||||
|
|
||||||
private Boolean hasSound;
|
|
||||||
private Boolean hasMotion;
|
|
||||||
private Boolean hasPerson;
|
|
||||||
private Date startTime;
|
|
||||||
private Date endTime;
|
|
||||||
private Date urlsExpireTime;
|
|
||||||
private String webUrl;
|
|
||||||
private String appUrl;
|
|
||||||
private String imageUrl;
|
|
||||||
private String animatedImageUrl;
|
|
||||||
private List<String> activityZoneIds;
|
|
||||||
|
|
||||||
public Boolean isHasSound() {
|
|
||||||
return hasSound;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isHasMotion() {
|
|
||||||
return hasMotion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isHasPerson() {
|
|
||||||
return hasPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getStartTime() {
|
|
||||||
return startTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getEndTime() {
|
|
||||||
return endTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getUrlsExpireTime() {
|
|
||||||
return urlsExpireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getWebUrl() {
|
|
||||||
return webUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAppUrl() {
|
|
||||||
return appUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getImageUrl() {
|
|
||||||
return imageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAnimatedImageUrl() {
|
|
||||||
return animatedImageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getActivityZones() {
|
|
||||||
return activityZoneIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNCameraEvent other = (WWNCameraEvent) obj;
|
|
||||||
if (activityZoneIds == null) {
|
|
||||||
if (other.activityZoneIds != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!activityZoneIds.equals(other.activityZoneIds)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (animatedImageUrl == null) {
|
|
||||||
if (other.animatedImageUrl != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!animatedImageUrl.equals(other.animatedImageUrl)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (appUrl == null) {
|
|
||||||
if (other.appUrl != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!appUrl.equals(other.appUrl)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (endTime == null) {
|
|
||||||
if (other.endTime != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!endTime.equals(other.endTime)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasMotion == null) {
|
|
||||||
if (other.hasMotion != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!hasMotion.equals(other.hasMotion)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasPerson == null) {
|
|
||||||
if (other.hasPerson != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!hasPerson.equals(other.hasPerson)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasSound == null) {
|
|
||||||
if (other.hasSound != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!hasSound.equals(other.hasSound)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (imageUrl == null) {
|
|
||||||
if (other.imageUrl != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!imageUrl.equals(other.imageUrl)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (startTime == null) {
|
|
||||||
if (other.startTime != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!startTime.equals(other.startTime)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (urlsExpireTime == null) {
|
|
||||||
if (other.urlsExpireTime != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!urlsExpireTime.equals(other.urlsExpireTime)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (webUrl == null) {
|
|
||||||
if (other.webUrl != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!webUrl.equals(other.webUrl)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((activityZoneIds == null) ? 0 : activityZoneIds.hashCode());
|
|
||||||
result = prime * result + ((animatedImageUrl == null) ? 0 : animatedImageUrl.hashCode());
|
|
||||||
result = prime * result + ((appUrl == null) ? 0 : appUrl.hashCode());
|
|
||||||
result = prime * result + ((endTime == null) ? 0 : endTime.hashCode());
|
|
||||||
result = prime * result + ((hasMotion == null) ? 0 : hasMotion.hashCode());
|
|
||||||
result = prime * result + ((hasPerson == null) ? 0 : hasPerson.hashCode());
|
|
||||||
result = prime * result + ((hasSound == null) ? 0 : hasSound.hashCode());
|
|
||||||
result = prime * result + ((imageUrl == null) ? 0 : imageUrl.hashCode());
|
|
||||||
result = prime * result + ((startTime == null) ? 0 : startTime.hashCode());
|
|
||||||
result = prime * result + ((urlsExpireTime == null) ? 0 : urlsExpireTime.hashCode());
|
|
||||||
result = prime * result + ((webUrl == null) ? 0 : webUrl.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("Event [hasSound=").append(hasSound).append(", hasMotion=").append(hasMotion)
|
|
||||||
.append(", hasPerson=").append(hasPerson).append(", startTime=").append(startTime).append(", endTime=")
|
|
||||||
.append(endTime).append(", urlsExpireTime=").append(urlsExpireTime).append(", webUrl=").append(webUrl)
|
|
||||||
.append(", appUrl=").append(appUrl).append(", imageUrl=").append(imageUrl).append(", animatedImageUrl=")
|
|
||||||
.append(animatedImageUrl).append(", activityZoneIds=").append(activityZoneIds).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All the WWN devices broken up by type.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
*/
|
|
||||||
public class WWNDevices {
|
|
||||||
|
|
||||||
private Map<String, WWNThermostat> thermostats;
|
|
||||||
private Map<String, WWNSmokeDetector> smokeCoAlarms;
|
|
||||||
private Map<String, WWNCamera> cameras;
|
|
||||||
|
|
||||||
/** Id to thermostat mapping */
|
|
||||||
public Map<String, WWNThermostat> getThermostats() {
|
|
||||||
return thermostats;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Id to camera mapping */
|
|
||||||
public Map<String, WWNCamera> getCameras() {
|
|
||||||
return cameras;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Id to smoke detector */
|
|
||||||
public Map<String, WWNSmokeDetector> getSmokeCoAlarms() {
|
|
||||||
return smokeCoAlarms;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNDevices other = (WWNDevices) obj;
|
|
||||||
if (cameras == null) {
|
|
||||||
if (other.cameras != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!cameras.equals(other.cameras)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (smokeCoAlarms == null) {
|
|
||||||
if (other.smokeCoAlarms != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!smokeCoAlarms.equals(other.smokeCoAlarms)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (thermostats == null) {
|
|
||||||
if (other.thermostats != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!thermostats.equals(other.thermostats)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((cameras == null) ? 0 : cameras.hashCode());
|
|
||||||
result = prime * result + ((smokeCoAlarms == null) ? 0 : smokeCoAlarms.hashCode());
|
|
||||||
result = prime * result + ((thermostats == null) ? 0 : thermostats.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("NestDevices [thermostats=").append(thermostats).append(", smokeCoAlarms=").append(smokeCoAlarms)
|
|
||||||
.append(", cameras=").append(cameras).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to set and update the WWN ETA values.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Extract ETA object from Structure
|
|
||||||
* @author Wouter Born - Add equals, hashCode, toString methods
|
|
||||||
*/
|
|
||||||
public class WWNETA {
|
|
||||||
|
|
||||||
private String tripId;
|
|
||||||
private Date estimatedArrivalWindowBegin;
|
|
||||||
private Date estimatedArrivalWindowEnd;
|
|
||||||
|
|
||||||
public String getTripId() {
|
|
||||||
return tripId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTripId(String tripId) {
|
|
||||||
this.tripId = tripId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getEstimatedArrivalWindowBegin() {
|
|
||||||
return estimatedArrivalWindowBegin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEstimatedArrivalWindowBegin(Date estimatedArrivalWindowBegin) {
|
|
||||||
this.estimatedArrivalWindowBegin = estimatedArrivalWindowBegin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getEstimatedArrivalWindowEnd() {
|
|
||||||
return estimatedArrivalWindowEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEstimatedArrivalWindowEnd(Date estimatedArrivalWindowEnd) {
|
|
||||||
this.estimatedArrivalWindowEnd = estimatedArrivalWindowEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNETA other = (WWNETA) obj;
|
|
||||||
if (estimatedArrivalWindowBegin == null) {
|
|
||||||
if (other.estimatedArrivalWindowBegin != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!estimatedArrivalWindowBegin.equals(other.estimatedArrivalWindowBegin)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (estimatedArrivalWindowEnd == null) {
|
|
||||||
if (other.estimatedArrivalWindowEnd != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!estimatedArrivalWindowEnd.equals(other.estimatedArrivalWindowEnd)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (tripId == null) {
|
|
||||||
if (other.tripId != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!tripId.equals(other.tripId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((estimatedArrivalWindowBegin == null) ? 0 : estimatedArrivalWindowBegin.hashCode());
|
|
||||||
result = prime * result + ((estimatedArrivalWindowEnd == null) ? 0 : estimatedArrivalWindowEnd.hashCode());
|
|
||||||
result = prime * result + ((tripId == null) ? 0 : tripId.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("ETA [tripId=").append(tripId).append(", estimatedArrivalWindowBegin=")
|
|
||||||
.append(estimatedArrivalWindowBegin).append(", estimatedArrivalWindowEnd=")
|
|
||||||
.append(estimatedArrivalWindowEnd).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The data of WWN API errors.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Improve exception handling
|
|
||||||
* @author Wouter Born - Add equals and hashCode methods
|
|
||||||
*/
|
|
||||||
public class WWNErrorData {
|
|
||||||
|
|
||||||
private String error;
|
|
||||||
private String type;
|
|
||||||
private String message;
|
|
||||||
private String instance;
|
|
||||||
|
|
||||||
public String getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNErrorData other = (WWNErrorData) obj;
|
|
||||||
if (error == null) {
|
|
||||||
if (other.error != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!error.equals(other.error)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (instance == null) {
|
|
||||||
if (other.instance != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!instance.equals(other.instance)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (message == null) {
|
|
||||||
if (other.message != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!message.equals(other.message)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (type == null) {
|
|
||||||
if (other.type != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!type.equals(other.type)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((error == null) ? 0 : error.hashCode());
|
|
||||||
result = prime * result + ((instance == null) ? 0 : instance.hashCode());
|
|
||||||
result = prime * result + ((message == null) ? 0 : message.hashCode());
|
|
||||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("ErrorData [error=").append(error).append(", type=").append(type).append(", message=")
|
|
||||||
.append(message).append(", instance=").append(instance).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for uniquely identifiable WWN objects (device or a structure).
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Simplify working with deviceId and structureId
|
|
||||||
*/
|
|
||||||
public interface WWNIdentifiable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the identifier that uniquely identifies the WWN object (deviceId or structureId).
|
|
||||||
*/
|
|
||||||
String getId();
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The WWN meta data in the data downloads.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Add equals and hashCode methods
|
|
||||||
*/
|
|
||||||
public class WWNMetadata {
|
|
||||||
|
|
||||||
private String accessToken;
|
|
||||||
private String clientVersion;
|
|
||||||
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClientVersion() {
|
|
||||||
return clientVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNMetadata other = (WWNMetadata) obj;
|
|
||||||
if (accessToken == null) {
|
|
||||||
if (other.accessToken != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!accessToken.equals(other.accessToken)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (clientVersion == null) {
|
|
||||||
if (other.clientVersion != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!clientVersion.equals(other.clientVersion)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((accessToken == null) ? 0 : accessToken.hashCode());
|
|
||||||
result = prime * result + ((clientVersion == null) ? 0 : clientVersion.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("NestMetadata [accessToken=").append(accessToken).append(", clientVersion=")
|
|
||||||
.append(clientVersion).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data for the WWN smoke detector.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Add equals and hashCode methods
|
|
||||||
*/
|
|
||||||
public class WWNSmokeDetector extends BaseWWNDevice {
|
|
||||||
|
|
||||||
private BatteryHealth batteryHealth;
|
|
||||||
private AlarmState coAlarmState;
|
|
||||||
private Date lastManualTestTime;
|
|
||||||
private AlarmState smokeAlarmState;
|
|
||||||
private Boolean isManualTestActive;
|
|
||||||
private UiColorState uiColorState;
|
|
||||||
|
|
||||||
public UiColorState getUiColorState() {
|
|
||||||
return uiColorState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BatteryHealth getBatteryHealth() {
|
|
||||||
return batteryHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlarmState getCoAlarmState() {
|
|
||||||
return coAlarmState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getLastManualTestTime() {
|
|
||||||
return lastManualTestTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlarmState getSmokeAlarmState() {
|
|
||||||
return smokeAlarmState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isManualTestActive() {
|
|
||||||
return isManualTestActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BatteryHealth {
|
|
||||||
@SerializedName("ok")
|
|
||||||
OK,
|
|
||||||
@SerializedName("replace")
|
|
||||||
REPLACE
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum AlarmState {
|
|
||||||
@SerializedName("ok")
|
|
||||||
OK,
|
|
||||||
@SerializedName("emergency")
|
|
||||||
EMERGENCY,
|
|
||||||
@SerializedName("warning")
|
|
||||||
WARNING
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum UiColorState {
|
|
||||||
@SerializedName("gray")
|
|
||||||
GRAY,
|
|
||||||
@SerializedName("green")
|
|
||||||
GREEN,
|
|
||||||
@SerializedName("yellow")
|
|
||||||
YELLOW,
|
|
||||||
@SerializedName("red")
|
|
||||||
RED
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || !super.equals(obj)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNSmokeDetector other = (WWNSmokeDetector) obj;
|
|
||||||
if (batteryHealth != other.batteryHealth) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (coAlarmState != other.coAlarmState) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isManualTestActive == null) {
|
|
||||||
if (other.isManualTestActive != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!isManualTestActive.equals(other.isManualTestActive)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lastManualTestTime == null) {
|
|
||||||
if (other.lastManualTestTime != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!lastManualTestTime.equals(other.lastManualTestTime)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (smokeAlarmState != other.smokeAlarmState) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return uiColorState == other.uiColorState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = super.hashCode();
|
|
||||||
result = prime * result + ((batteryHealth == null) ? 0 : batteryHealth.hashCode());
|
|
||||||
result = prime * result + ((coAlarmState == null) ? 0 : coAlarmState.hashCode());
|
|
||||||
result = prime * result + ((isManualTestActive == null) ? 0 : isManualTestActive.hashCode());
|
|
||||||
result = prime * result + ((lastManualTestTime == null) ? 0 : lastManualTestTime.hashCode());
|
|
||||||
result = prime * result + ((smokeAlarmState == null) ? 0 : smokeAlarmState.hashCode());
|
|
||||||
result = prime * result + ((uiColorState == null) ? 0 : uiColorState.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("SmokeDetector [batteryHealth=").append(batteryHealth).append(", coAlarmState=")
|
|
||||||
.append(coAlarmState).append(", lastManualTestTime=").append(lastManualTestTime)
|
|
||||||
.append(", smokeAlarmState=").append(smokeAlarmState).append(", isManualTestActive=")
|
|
||||||
.append(isManualTestActive).append(", uiColorState=").append(uiColorState).append(", getId()=")
|
|
||||||
.append(getId()).append(", getName()=").append(getName()).append(", getDeviceId()=")
|
|
||||||
.append(getDeviceId()).append(", getLastConnection()=").append(getLastConnection())
|
|
||||||
.append(", isOnline()=").append(isOnline()).append(", getNameLong()=").append(getNameLong())
|
|
||||||
.append(", getSoftwareVersion()=").append(getSoftwareVersion()).append(", getStructureId()=")
|
|
||||||
.append(getStructureId()).append(", getWhereId()=").append(getWhereId()).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,308 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNSmokeDetector.AlarmState;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The WWN structure details.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Add equals and hashCode methods
|
|
||||||
*/
|
|
||||||
public class WWNStructure implements WWNIdentifiable {
|
|
||||||
|
|
||||||
private String structureId;
|
|
||||||
private List<String> thermostats;
|
|
||||||
private List<String> smokeCoAlarms;
|
|
||||||
private List<String> cameras;
|
|
||||||
private String countryCode;
|
|
||||||
private String postalCode;
|
|
||||||
private Date peakPeriodStartTime;
|
|
||||||
private Date peakPeriodEndTime;
|
|
||||||
private String timeZone;
|
|
||||||
private Date etaBegin;
|
|
||||||
private WWNSmokeDetector.AlarmState coAlarmState;
|
|
||||||
private WWNSmokeDetector.AlarmState smokeAlarmState;
|
|
||||||
private Boolean rhrEnrollment;
|
|
||||||
private Map<String, WWNWhere> wheres;
|
|
||||||
private HomeAwayState away;
|
|
||||||
private String name;
|
|
||||||
private WWNETA eta;
|
|
||||||
private SecurityState wwnSecurityState;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return structureId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HomeAwayState getAway() {
|
|
||||||
return away;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAway(HomeAwayState away) {
|
|
||||||
this.away = away;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStructureId() {
|
|
||||||
return structureId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getThermostats() {
|
|
||||||
return thermostats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getSmokeCoAlarms() {
|
|
||||||
return smokeCoAlarms;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getCameras() {
|
|
||||||
return cameras;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCountryCode() {
|
|
||||||
return countryCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPostalCode() {
|
|
||||||
return postalCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getPeakPeriodStartTime() {
|
|
||||||
return peakPeriodStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getPeakPeriodEndTime() {
|
|
||||||
return peakPeriodEndTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTimeZone() {
|
|
||||||
return timeZone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getEtaBegin() {
|
|
||||||
return etaBegin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlarmState getCoAlarmState() {
|
|
||||||
return coAlarmState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlarmState getSmokeAlarmState() {
|
|
||||||
return smokeAlarmState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isRhrEnrollment() {
|
|
||||||
return rhrEnrollment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, WWNWhere> getWheres() {
|
|
||||||
return wheres;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WWNETA getEta() {
|
|
||||||
return eta;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SecurityState getWwnSecurityState() {
|
|
||||||
return wwnSecurityState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum HomeAwayState {
|
|
||||||
@SerializedName("home")
|
|
||||||
HOME,
|
|
||||||
@SerializedName("away")
|
|
||||||
AWAY,
|
|
||||||
@SerializedName("unknown")
|
|
||||||
UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SecurityState {
|
|
||||||
@SerializedName("ok")
|
|
||||||
OK,
|
|
||||||
@SerializedName("deter")
|
|
||||||
DETER
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNStructure other = (WWNStructure) obj;
|
|
||||||
if (away != other.away) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (cameras == null) {
|
|
||||||
if (other.cameras != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!cameras.equals(other.cameras)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (coAlarmState != other.coAlarmState) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (countryCode == null) {
|
|
||||||
if (other.countryCode != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!countryCode.equals(other.countryCode)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (eta == null) {
|
|
||||||
if (other.eta != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!eta.equals(other.eta)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (etaBegin == null) {
|
|
||||||
if (other.etaBegin != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!etaBegin.equals(other.etaBegin)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (name == null) {
|
|
||||||
if (other.name != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!name.equals(other.name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (peakPeriodEndTime == null) {
|
|
||||||
if (other.peakPeriodEndTime != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!peakPeriodEndTime.equals(other.peakPeriodEndTime)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (peakPeriodStartTime == null) {
|
|
||||||
if (other.peakPeriodStartTime != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!peakPeriodStartTime.equals(other.peakPeriodStartTime)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (postalCode == null) {
|
|
||||||
if (other.postalCode != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!postalCode.equals(other.postalCode)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (rhrEnrollment == null) {
|
|
||||||
if (other.rhrEnrollment != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!rhrEnrollment.equals(other.rhrEnrollment)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (smokeAlarmState != other.smokeAlarmState) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (smokeCoAlarms == null) {
|
|
||||||
if (other.smokeCoAlarms != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!smokeCoAlarms.equals(other.smokeCoAlarms)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (structureId == null) {
|
|
||||||
if (other.structureId != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!structureId.equals(other.structureId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (thermostats == null) {
|
|
||||||
if (other.thermostats != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!thermostats.equals(other.thermostats)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (timeZone == null) {
|
|
||||||
if (other.timeZone != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!timeZone.equals(other.timeZone)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (wheres == null) {
|
|
||||||
if (other.wheres != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!wheres.equals(other.wheres)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return wwnSecurityState == other.wwnSecurityState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((away == null) ? 0 : away.hashCode());
|
|
||||||
result = prime * result + ((cameras == null) ? 0 : cameras.hashCode());
|
|
||||||
result = prime * result + ((coAlarmState == null) ? 0 : coAlarmState.hashCode());
|
|
||||||
result = prime * result + ((countryCode == null) ? 0 : countryCode.hashCode());
|
|
||||||
result = prime * result + ((eta == null) ? 0 : eta.hashCode());
|
|
||||||
result = prime * result + ((etaBegin == null) ? 0 : etaBegin.hashCode());
|
|
||||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
|
||||||
result = prime * result + ((peakPeriodEndTime == null) ? 0 : peakPeriodEndTime.hashCode());
|
|
||||||
result = prime * result + ((peakPeriodStartTime == null) ? 0 : peakPeriodStartTime.hashCode());
|
|
||||||
result = prime * result + ((postalCode == null) ? 0 : postalCode.hashCode());
|
|
||||||
result = prime * result + ((rhrEnrollment == null) ? 0 : rhrEnrollment.hashCode());
|
|
||||||
result = prime * result + ((smokeAlarmState == null) ? 0 : smokeAlarmState.hashCode());
|
|
||||||
result = prime * result + ((smokeCoAlarms == null) ? 0 : smokeCoAlarms.hashCode());
|
|
||||||
result = prime * result + ((structureId == null) ? 0 : structureId.hashCode());
|
|
||||||
result = prime * result + ((thermostats == null) ? 0 : thermostats.hashCode());
|
|
||||||
result = prime * result + ((timeZone == null) ? 0 : timeZone.hashCode());
|
|
||||||
result = prime * result + ((wheres == null) ? 0 : wheres.hashCode());
|
|
||||||
result = prime * result + ((wwnSecurityState == null) ? 0 : wwnSecurityState.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("Structure [structureId=").append(structureId).append(", thermostats=").append(thermostats)
|
|
||||||
.append(", smokeCoAlarms=").append(smokeCoAlarms).append(", cameras=").append(cameras)
|
|
||||||
.append(", countryCode=").append(countryCode).append(", postalCode=").append(postalCode)
|
|
||||||
.append(", peakPeriodStartTime=").append(peakPeriodStartTime).append(", peakPeriodEndTime=")
|
|
||||||
.append(peakPeriodEndTime).append(", timeZone=").append(timeZone).append(", etaBegin=").append(etaBegin)
|
|
||||||
.append(", coAlarmState=").append(coAlarmState).append(", smokeAlarmState=").append(smokeAlarmState)
|
|
||||||
.append(", rhrEnrollment=").append(rhrEnrollment).append(", wheres=").append(wheres).append(", away=")
|
|
||||||
.append(away).append(", name=").append(name).append(", eta=").append(eta).append(", wwnSecurityState=")
|
|
||||||
.append(wwnSecurityState).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,572 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import static org.openhab.core.library.unit.ImperialUnits.FAHRENHEIT;
|
|
||||||
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import javax.measure.Unit;
|
|
||||||
import javax.measure.quantity.Temperature;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gson class to encapsulate the data for the WWN thermostat.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Add equals and hashCode methods
|
|
||||||
*/
|
|
||||||
public class WWNThermostat extends BaseWWNDevice {
|
|
||||||
|
|
||||||
private Boolean canCool;
|
|
||||||
private Boolean canHeat;
|
|
||||||
private Boolean isUsingEmergencyHeat;
|
|
||||||
private Boolean hasFan;
|
|
||||||
private Boolean fanTimerActive;
|
|
||||||
private Date fanTimerTimeout;
|
|
||||||
private Boolean hasLeaf;
|
|
||||||
private String temperatureScale;
|
|
||||||
private Double ambientTemperatureC;
|
|
||||||
private Double ambientTemperatureF;
|
|
||||||
private Integer humidity;
|
|
||||||
private Double targetTemperatureC;
|
|
||||||
private Double targetTemperatureF;
|
|
||||||
private Double targetTemperatureHighC;
|
|
||||||
private Double targetTemperatureHighF;
|
|
||||||
private Double targetTemperatureLowC;
|
|
||||||
private Double targetTemperatureLowF;
|
|
||||||
private Mode hvacMode;
|
|
||||||
private Mode previousHvacMode;
|
|
||||||
private State hvacState;
|
|
||||||
private Double ecoTemperatureHighC;
|
|
||||||
private Double ecoTemperatureHighF;
|
|
||||||
private Double ecoTemperatureLowC;
|
|
||||||
private Double ecoTemperatureLowF;
|
|
||||||
private Boolean isLocked;
|
|
||||||
private Double lockedTempMaxC;
|
|
||||||
private Double lockedTempMaxF;
|
|
||||||
private Double lockedTempMinC;
|
|
||||||
private Double lockedTempMinF;
|
|
||||||
private Boolean sunlightCorrectionEnabled;
|
|
||||||
private Boolean sunlightCorrectionActive;
|
|
||||||
private Integer fanTimerDuration;
|
|
||||||
private String timeToTarget;
|
|
||||||
private String whereName;
|
|
||||||
|
|
||||||
public Unit<Temperature> getTemperatureUnit() {
|
|
||||||
if ("C".equals(temperatureScale)) {
|
|
||||||
return CELSIUS;
|
|
||||||
} else if ("F".equals(temperatureScale)) {
|
|
||||||
return FAHRENHEIT;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getTargetTemperature() {
|
|
||||||
if (getTemperatureUnit() == CELSIUS) {
|
|
||||||
return targetTemperatureC;
|
|
||||||
} else if (getTemperatureUnit() == FAHRENHEIT) {
|
|
||||||
return targetTemperatureF;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getTargetTemperatureHigh() {
|
|
||||||
if (getTemperatureUnit() == CELSIUS) {
|
|
||||||
return targetTemperatureHighC;
|
|
||||||
} else if (getTemperatureUnit() == FAHRENHEIT) {
|
|
||||||
return targetTemperatureHighF;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getTargetTemperatureLow() {
|
|
||||||
if (getTemperatureUnit() == CELSIUS) {
|
|
||||||
return targetTemperatureLowC;
|
|
||||||
} else if (getTemperatureUnit() == FAHRENHEIT) {
|
|
||||||
return targetTemperatureLowF;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Mode getMode() {
|
|
||||||
return hvacMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getEcoTemperatureHigh() {
|
|
||||||
if (getTemperatureUnit() == CELSIUS) {
|
|
||||||
return ecoTemperatureHighC;
|
|
||||||
} else if (getTemperatureUnit() == FAHRENHEIT) {
|
|
||||||
return ecoTemperatureHighF;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getEcoTemperatureLow() {
|
|
||||||
if (getTemperatureUnit() == CELSIUS) {
|
|
||||||
return ecoTemperatureLowC;
|
|
||||||
} else if (getTemperatureUnit() == FAHRENHEIT) {
|
|
||||||
return ecoTemperatureLowF;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isLocked() {
|
|
||||||
return isLocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getLockedTempMax() {
|
|
||||||
if (getTemperatureUnit() == CELSIUS) {
|
|
||||||
return lockedTempMaxC;
|
|
||||||
} else if (getTemperatureUnit() == FAHRENHEIT) {
|
|
||||||
return lockedTempMaxF;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getLockedTempMin() {
|
|
||||||
if (getTemperatureUnit() == CELSIUS) {
|
|
||||||
return lockedTempMinC;
|
|
||||||
} else if (getTemperatureUnit() == FAHRENHEIT) {
|
|
||||||
return lockedTempMinF;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isCanCool() {
|
|
||||||
return canCool;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isCanHeat() {
|
|
||||||
return canHeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isUsingEmergencyHeat() {
|
|
||||||
return isUsingEmergencyHeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isHasFan() {
|
|
||||||
return hasFan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isFanTimerActive() {
|
|
||||||
return fanTimerActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getFanTimerTimeout() {
|
|
||||||
return fanTimerTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isHasLeaf() {
|
|
||||||
return hasLeaf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Mode getPreviousHvacMode() {
|
|
||||||
return previousHvacMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public State getHvacState() {
|
|
||||||
return hvacState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isSunlightCorrectionEnabled() {
|
|
||||||
return sunlightCorrectionEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isSunlightCorrectionActive() {
|
|
||||||
return sunlightCorrectionActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getFanTimerDuration() {
|
|
||||||
return fanTimerDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getTimeToTarget() {
|
|
||||||
return parseTimeToTarget(timeToTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Turns the time to target string into a real value.
|
|
||||||
*/
|
|
||||||
static Integer parseTimeToTarget(String timeToTarget) {
|
|
||||||
if (timeToTarget == null) {
|
|
||||||
return null;
|
|
||||||
} else if (timeToTarget.startsWith("~") || timeToTarget.startsWith("<") || timeToTarget.startsWith(">")) {
|
|
||||||
return Integer.valueOf(timeToTarget.substring(1));
|
|
||||||
}
|
|
||||||
return Integer.valueOf(timeToTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getWhereName() {
|
|
||||||
return whereName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getAmbientTemperature() {
|
|
||||||
if (getTemperatureUnit() == CELSIUS) {
|
|
||||||
return ambientTemperatureC;
|
|
||||||
} else if (getTemperatureUnit() == FAHRENHEIT) {
|
|
||||||
return ambientTemperatureF;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getHumidity() {
|
|
||||||
return humidity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Mode {
|
|
||||||
@SerializedName("heat")
|
|
||||||
HEAT,
|
|
||||||
@SerializedName("cool")
|
|
||||||
COOL,
|
|
||||||
@SerializedName("heat-cool")
|
|
||||||
HEAT_COOL,
|
|
||||||
@SerializedName("eco")
|
|
||||||
ECO,
|
|
||||||
@SerializedName("off")
|
|
||||||
OFF
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum State {
|
|
||||||
@SerializedName("heating")
|
|
||||||
HEATING,
|
|
||||||
@SerializedName("cooling")
|
|
||||||
COOLING,
|
|
||||||
@SerializedName("off")
|
|
||||||
OFF
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || !super.equals(obj)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNThermostat other = (WWNThermostat) obj;
|
|
||||||
if (ambientTemperatureC == null) {
|
|
||||||
if (other.ambientTemperatureC != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!ambientTemperatureC.equals(other.ambientTemperatureC)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ambientTemperatureF == null) {
|
|
||||||
if (other.ambientTemperatureF != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!ambientTemperatureF.equals(other.ambientTemperatureF)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (canCool == null) {
|
|
||||||
if (other.canCool != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!canCool.equals(other.canCool)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (canHeat == null) {
|
|
||||||
if (other.canHeat != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!canHeat.equals(other.canHeat)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ecoTemperatureHighC == null) {
|
|
||||||
if (other.ecoTemperatureHighC != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!ecoTemperatureHighC.equals(other.ecoTemperatureHighC)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ecoTemperatureHighF == null) {
|
|
||||||
if (other.ecoTemperatureHighF != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!ecoTemperatureHighF.equals(other.ecoTemperatureHighF)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ecoTemperatureLowC == null) {
|
|
||||||
if (other.ecoTemperatureLowC != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!ecoTemperatureLowC.equals(other.ecoTemperatureLowC)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ecoTemperatureLowF == null) {
|
|
||||||
if (other.ecoTemperatureLowF != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!ecoTemperatureLowF.equals(other.ecoTemperatureLowF)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (fanTimerActive == null) {
|
|
||||||
if (other.fanTimerActive != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!fanTimerActive.equals(other.fanTimerActive)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (fanTimerDuration == null) {
|
|
||||||
if (other.fanTimerDuration != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!fanTimerDuration.equals(other.fanTimerDuration)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (fanTimerTimeout == null) {
|
|
||||||
if (other.fanTimerTimeout != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!fanTimerTimeout.equals(other.fanTimerTimeout)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasFan == null) {
|
|
||||||
if (other.hasFan != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!hasFan.equals(other.hasFan)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasLeaf == null) {
|
|
||||||
if (other.hasLeaf != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!hasLeaf.equals(other.hasLeaf)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (humidity == null) {
|
|
||||||
if (other.humidity != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!humidity.equals(other.humidity)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hvacMode != other.hvacMode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hvacState != other.hvacState) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isLocked == null) {
|
|
||||||
if (other.isLocked != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!isLocked.equals(other.isLocked)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isUsingEmergencyHeat == null) {
|
|
||||||
if (other.isUsingEmergencyHeat != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!isUsingEmergencyHeat.equals(other.isUsingEmergencyHeat)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lockedTempMaxC == null) {
|
|
||||||
if (other.lockedTempMaxC != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!lockedTempMaxC.equals(other.lockedTempMaxC)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lockedTempMaxF == null) {
|
|
||||||
if (other.lockedTempMaxF != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!lockedTempMaxF.equals(other.lockedTempMaxF)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lockedTempMinC == null) {
|
|
||||||
if (other.lockedTempMinC != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!lockedTempMinC.equals(other.lockedTempMinC)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lockedTempMinF == null) {
|
|
||||||
if (other.lockedTempMinF != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!lockedTempMinF.equals(other.lockedTempMinF)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (previousHvacMode != other.previousHvacMode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (sunlightCorrectionActive == null) {
|
|
||||||
if (other.sunlightCorrectionActive != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!sunlightCorrectionActive.equals(other.sunlightCorrectionActive)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (sunlightCorrectionEnabled == null) {
|
|
||||||
if (other.sunlightCorrectionEnabled != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!sunlightCorrectionEnabled.equals(other.sunlightCorrectionEnabled)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (targetTemperatureC == null) {
|
|
||||||
if (other.targetTemperatureC != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!targetTemperatureC.equals(other.targetTemperatureC)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (targetTemperatureF == null) {
|
|
||||||
if (other.targetTemperatureF != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!targetTemperatureF.equals(other.targetTemperatureF)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (targetTemperatureHighC == null) {
|
|
||||||
if (other.targetTemperatureHighC != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!targetTemperatureHighC.equals(other.targetTemperatureHighC)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (targetTemperatureHighF == null) {
|
|
||||||
if (other.targetTemperatureHighF != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!targetTemperatureHighF.equals(other.targetTemperatureHighF)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (targetTemperatureLowC == null) {
|
|
||||||
if (other.targetTemperatureLowC != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!targetTemperatureLowC.equals(other.targetTemperatureLowC)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (targetTemperatureLowF == null) {
|
|
||||||
if (other.targetTemperatureLowF != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!targetTemperatureLowF.equals(other.targetTemperatureLowF)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (temperatureScale == null) {
|
|
||||||
if (other.temperatureScale != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!temperatureScale.equals(other.temperatureScale)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (timeToTarget == null) {
|
|
||||||
if (other.timeToTarget != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!timeToTarget.equals(other.timeToTarget)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (whereName == null) {
|
|
||||||
if (other.whereName != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!whereName.equals(other.whereName)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = super.hashCode();
|
|
||||||
result = prime * result + ((ambientTemperatureC == null) ? 0 : ambientTemperatureC.hashCode());
|
|
||||||
result = prime * result + ((ambientTemperatureF == null) ? 0 : ambientTemperatureF.hashCode());
|
|
||||||
result = prime * result + ((canCool == null) ? 0 : canCool.hashCode());
|
|
||||||
result = prime * result + ((canHeat == null) ? 0 : canHeat.hashCode());
|
|
||||||
result = prime * result + ((ecoTemperatureHighC == null) ? 0 : ecoTemperatureHighC.hashCode());
|
|
||||||
result = prime * result + ((ecoTemperatureHighF == null) ? 0 : ecoTemperatureHighF.hashCode());
|
|
||||||
result = prime * result + ((ecoTemperatureLowC == null) ? 0 : ecoTemperatureLowC.hashCode());
|
|
||||||
result = prime * result + ((ecoTemperatureLowF == null) ? 0 : ecoTemperatureLowF.hashCode());
|
|
||||||
result = prime * result + ((fanTimerActive == null) ? 0 : fanTimerActive.hashCode());
|
|
||||||
result = prime * result + ((fanTimerDuration == null) ? 0 : fanTimerDuration.hashCode());
|
|
||||||
result = prime * result + ((fanTimerTimeout == null) ? 0 : fanTimerTimeout.hashCode());
|
|
||||||
result = prime * result + ((hasFan == null) ? 0 : hasFan.hashCode());
|
|
||||||
result = prime * result + ((hasLeaf == null) ? 0 : hasLeaf.hashCode());
|
|
||||||
result = prime * result + ((humidity == null) ? 0 : humidity.hashCode());
|
|
||||||
result = prime * result + ((hvacMode == null) ? 0 : hvacMode.hashCode());
|
|
||||||
result = prime * result + ((hvacState == null) ? 0 : hvacState.hashCode());
|
|
||||||
result = prime * result + ((isLocked == null) ? 0 : isLocked.hashCode());
|
|
||||||
result = prime * result + ((isUsingEmergencyHeat == null) ? 0 : isUsingEmergencyHeat.hashCode());
|
|
||||||
result = prime * result + ((lockedTempMaxC == null) ? 0 : lockedTempMaxC.hashCode());
|
|
||||||
result = prime * result + ((lockedTempMaxF == null) ? 0 : lockedTempMaxF.hashCode());
|
|
||||||
result = prime * result + ((lockedTempMinC == null) ? 0 : lockedTempMinC.hashCode());
|
|
||||||
result = prime * result + ((lockedTempMinF == null) ? 0 : lockedTempMinF.hashCode());
|
|
||||||
result = prime * result + ((previousHvacMode == null) ? 0 : previousHvacMode.hashCode());
|
|
||||||
result = prime * result + ((sunlightCorrectionActive == null) ? 0 : sunlightCorrectionActive.hashCode());
|
|
||||||
result = prime * result + ((sunlightCorrectionEnabled == null) ? 0 : sunlightCorrectionEnabled.hashCode());
|
|
||||||
result = prime * result + ((targetTemperatureC == null) ? 0 : targetTemperatureC.hashCode());
|
|
||||||
result = prime * result + ((targetTemperatureF == null) ? 0 : targetTemperatureF.hashCode());
|
|
||||||
result = prime * result + ((targetTemperatureHighC == null) ? 0 : targetTemperatureHighC.hashCode());
|
|
||||||
result = prime * result + ((targetTemperatureHighF == null) ? 0 : targetTemperatureHighF.hashCode());
|
|
||||||
result = prime * result + ((targetTemperatureLowC == null) ? 0 : targetTemperatureLowC.hashCode());
|
|
||||||
result = prime * result + ((targetTemperatureLowF == null) ? 0 : targetTemperatureLowF.hashCode());
|
|
||||||
result = prime * result + ((temperatureScale == null) ? 0 : temperatureScale.hashCode());
|
|
||||||
result = prime * result + ((timeToTarget == null) ? 0 : timeToTarget.hashCode());
|
|
||||||
result = prime * result + ((whereName == null) ? 0 : whereName.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("Thermostat [canCool=").append(canCool).append(", canHeat=").append(canHeat)
|
|
||||||
.append(", isUsingEmergencyHeat=").append(isUsingEmergencyHeat).append(", hasFan=").append(hasFan)
|
|
||||||
.append(", fanTimerActive=").append(fanTimerActive).append(", fanTimerTimeout=").append(fanTimerTimeout)
|
|
||||||
.append(", hasLeaf=").append(hasLeaf).append(", temperatureScale=").append(temperatureScale)
|
|
||||||
.append(", ambientTemperatureC=").append(ambientTemperatureC).append(", ambientTemperatureF=")
|
|
||||||
.append(ambientTemperatureF).append(", humidity=").append(humidity).append(", targetTemperatureC=")
|
|
||||||
.append(targetTemperatureC).append(", targetTemperatureF=").append(targetTemperatureF)
|
|
||||||
.append(", targetTemperatureHighC=").append(targetTemperatureHighC).append(", targetTemperatureHighF=")
|
|
||||||
.append(targetTemperatureHighF).append(", targetTemperatureLowC=").append(targetTemperatureLowC)
|
|
||||||
.append(", targetTemperatureLowF=").append(targetTemperatureLowF).append(", hvacMode=").append(hvacMode)
|
|
||||||
.append(", previousHvacMode=").append(previousHvacMode).append(", hvacState=").append(hvacState)
|
|
||||||
.append(", ecoTemperatureHighC=").append(ecoTemperatureHighC).append(", ecoTemperatureHighF=")
|
|
||||||
.append(ecoTemperatureHighF).append(", ecoTemperatureLowC=").append(ecoTemperatureLowC)
|
|
||||||
.append(", ecoTemperatureLowF=").append(ecoTemperatureLowF).append(", isLocked=").append(isLocked)
|
|
||||||
.append(", lockedTempMaxC=").append(lockedTempMaxC).append(", lockedTempMaxF=").append(lockedTempMaxF)
|
|
||||||
.append(", lockedTempMinC=").append(lockedTempMinC).append(", lockedTempMinF=").append(lockedTempMinF)
|
|
||||||
.append(", sunlightCorrectionEnabled=").append(sunlightCorrectionEnabled)
|
|
||||||
.append(", sunlightCorrectionActive=").append(sunlightCorrectionActive).append(", fanTimerDuration=")
|
|
||||||
.append(fanTimerDuration).append(", timeToTarget=").append(timeToTarget).append(", whereName=")
|
|
||||||
.append(whereName).append(", getId()=").append(getId()).append(", getName()=").append(getName())
|
|
||||||
.append(", getDeviceId()=").append(getDeviceId()).append(", getLastConnection()=")
|
|
||||||
.append(getLastConnection()).append(", isOnline()=").append(isOnline()).append(", getNameLong()=")
|
|
||||||
.append(getNameLong()).append(", getSoftwareVersion()=").append(getSoftwareVersion())
|
|
||||||
.append(", getStructureId()=").append(getStructureId()).append(", getWhereId()=").append(getWhereId())
|
|
||||||
.append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The top level WWN data that is sent by Nest.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Add equals and hashCode methods
|
|
||||||
*/
|
|
||||||
public class WWNTopLevelData {
|
|
||||||
|
|
||||||
private WWNDevices devices;
|
|
||||||
private WWNMetadata metadata;
|
|
||||||
private Map<String, WWNStructure> structures;
|
|
||||||
|
|
||||||
public WWNDevices getDevices() {
|
|
||||||
return devices;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WWNMetadata getMetadata() {
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, WWNStructure> getStructures() {
|
|
||||||
return structures;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNTopLevelData other = (WWNTopLevelData) obj;
|
|
||||||
if (devices == null) {
|
|
||||||
if (other.devices != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!devices.equals(other.devices)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (metadata == null) {
|
|
||||||
if (other.metadata != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!metadata.equals(other.metadata)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (structures == null) {
|
|
||||||
if (other.structures != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!structures.equals(other.structures)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((devices == null) ? 0 : devices.hashCode());
|
|
||||||
result = prime * result + ((metadata == null) ? 0 : metadata.hashCode());
|
|
||||||
result = prime * result + ((structures == null) ? 0 : structures.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("TopLevelData [devices=").append(devices).append(", metadata=").append(metadata)
|
|
||||||
.append(", structures=").append(structures).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The top level WWN data that is sent by Nest to a streaming REST client using SSE.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Replace polling with REST streaming
|
|
||||||
* @author Wouter Born - Add equals and hashCode methods
|
|
||||||
*/
|
|
||||||
public class WWNTopLevelStreamingData {
|
|
||||||
|
|
||||||
private String path;
|
|
||||||
private WWNTopLevelData data;
|
|
||||||
|
|
||||||
public String getPath() {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WWNTopLevelData getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((data == null) ? 0 : data.hashCode());
|
|
||||||
result = prime * result + ((path == null) ? 0 : path.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNTopLevelStreamingData other = (WWNTopLevelStreamingData) obj;
|
|
||||||
if (data == null) {
|
|
||||||
if (other.data != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!data.equals(other.data)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (path == null) {
|
|
||||||
if (other.path != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!path.equals(other.path)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("TopLevelStreamingData [path=").append(path).append(", data=").append(data).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains the data needed to do a WWN update request back to Nest.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
*/
|
|
||||||
public class WWNUpdateRequest {
|
|
||||||
private final String updatePath;
|
|
||||||
private final Map<String, Object> values;
|
|
||||||
|
|
||||||
private WWNUpdateRequest(Builder builder) {
|
|
||||||
this.updatePath = builder.basePath + builder.identifier;
|
|
||||||
this.values = builder.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUpdatePath() {
|
|
||||||
return updatePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> getValues() {
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private String basePath;
|
|
||||||
private String identifier;
|
|
||||||
private Map<String, Object> values = new HashMap<>();
|
|
||||||
|
|
||||||
public Builder withBasePath(String basePath) {
|
|
||||||
this.basePath = basePath;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withIdentifier(String identifier) {
|
|
||||||
this.identifier = identifier;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withAdditionalValue(String field, Object value) {
|
|
||||||
values.put(field, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WWNUpdateRequest build() {
|
|
||||||
return new WWNUpdateRequest(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Extract Where object from Structure
|
|
||||||
* @author Wouter Born - Add equals, hashCode, toString methods
|
|
||||||
*/
|
|
||||||
public class WWNWhere {
|
|
||||||
private String whereId;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
public String getWhereId() {
|
|
||||||
return whereId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WWNWhere other = (WWNWhere) obj;
|
|
||||||
if (name == null) {
|
|
||||||
if (other.name != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!name.equals(other.name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (whereId == null) {
|
|
||||||
if (other.whereId != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!whereId.equals(other.whereId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
|
||||||
result = prime * result + ((whereId == null) ? 0 : whereId.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("Where [whereId=").append(whereId).append(", name=").append(name).append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.exceptions;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will be thrown when the bridge was unable to resolve the Nest redirect URL.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Improve exception handling while sending data
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class FailedResolvingWWNUrlException extends Exception {
|
|
||||||
public FailedResolvingWWNUrlException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FailedResolvingWWNUrlException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FailedResolvingWWNUrlException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.exceptions;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will be thrown when the bridge was unable to retrieve data.
|
|
||||||
*
|
|
||||||
* @author Martin van Wingerden - Initial contribution
|
|
||||||
* @author Martin van Wingerden - Added more centralized handling of failure when retrieving data
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class FailedRetrievingWWNDataException extends Exception {
|
|
||||||
|
|
||||||
public FailedRetrievingWWNDataException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FailedRetrievingWWNDataException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FailedRetrievingWWNDataException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.exceptions;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will be thrown when the bridge was unable to send data.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Improve exception handling while sending data
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class FailedSendingWWNDataException extends Exception {
|
|
||||||
public FailedSendingWWNDataException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FailedSendingWWNDataException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FailedSendingWWNDataException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.exceptions;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will be thrown when there is no valid access token and it was not possible to refresh it
|
|
||||||
*
|
|
||||||
* @author Martin van Wingerden - Initial contribution
|
|
||||||
* @author Martin van Wingerden - Added more centralized handling of invalid access tokens
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class InvalidWWNAccessTokenException extends Exception {
|
|
||||||
public InvalidWWNAccessTokenException(Exception cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidWWNAccessTokenException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidWWNAccessTokenException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,391 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.JSON_CONTENT_TYPE;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.WWNUtils;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNAccountConfiguration;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.discovery.WWNDiscoveryService;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNErrorData;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNIdentifiable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNTopLevelData;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNUpdateRequest;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.exceptions.FailedResolvingWWNUrlException;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.exceptions.FailedSendingWWNDataException;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.exceptions.InvalidWWNAccessTokenException;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.listener.WWNStreamingDataListener;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.listener.WWNThingDataListener;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.rest.WWNAuthorizer;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.update.WWNCompositeUpdateHandler;
|
|
||||||
import org.openhab.core.config.core.Configuration;
|
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
|
||||||
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.ThingHandler;
|
|
||||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
|
||||||
import org.openhab.core.types.Command;
|
|
||||||
import org.openhab.core.types.RefreshType;
|
|
||||||
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This account handler connects to Nest and handles all the WWN API requests. It pulls down the
|
|
||||||
* updated data, polls the system and does all the co-ordination with the other handlers
|
|
||||||
* to get the data updated to the correct things.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Martin van Wingerden - Use listeners not only for discovery but for all data processing
|
|
||||||
* @author Wouter Born - Improve exception and URL redirect handling
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNAccountHandler extends BaseBridgeHandler implements WWNStreamingDataListener {
|
|
||||||
|
|
||||||
private static final int REQUEST_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(30);
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNAccountHandler.class);
|
|
||||||
|
|
||||||
private final ClientBuilder clientBuilder;
|
|
||||||
private final SseEventSourceFactory eventSourceFactory;
|
|
||||||
private final List<WWNUpdateRequest> nestUpdateRequests = new CopyOnWriteArrayList<>();
|
|
||||||
private final WWNCompositeUpdateHandler updateHandler = new WWNCompositeUpdateHandler(
|
|
||||||
this::getPresentThingsNestIds);
|
|
||||||
|
|
||||||
private @NonNullByDefault({}) WWNAuthorizer authorizer;
|
|
||||||
private @NonNullByDefault({}) WWNAccountConfiguration config;
|
|
||||||
|
|
||||||
private @Nullable ScheduledFuture<?> initializeJob;
|
|
||||||
private @Nullable ScheduledFuture<?> transmitJob;
|
|
||||||
private @Nullable WWNRedirectUrlSupplier redirectUrlSupplier;
|
|
||||||
private @Nullable WWNStreamingRestClient streamingRestClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the bridge handler to connect to Nest.
|
|
||||||
*
|
|
||||||
* @param bridge The bridge to connect to Nest with.
|
|
||||||
*/
|
|
||||||
public WWNAccountHandler(Bridge bridge, ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory) {
|
|
||||||
super(bridge);
|
|
||||||
this.clientBuilder = clientBuilder;
|
|
||||||
this.eventSourceFactory = eventSourceFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the connection to Nest.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
logger.debug("Initializing Nest bridge handler");
|
|
||||||
|
|
||||||
config = getConfigAs(WWNAccountConfiguration.class);
|
|
||||||
authorizer = new WWNAuthorizer(config);
|
|
||||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Starting poll query");
|
|
||||||
|
|
||||||
initializeJob = scheduler.schedule(() -> {
|
|
||||||
try {
|
|
||||||
logger.debug("Product ID {}", config.productId);
|
|
||||||
logger.debug("Product Secret {}", config.productSecret);
|
|
||||||
logger.debug("Pincode {}", config.pincode);
|
|
||||||
logger.debug("Access Token {}", getExistingOrNewAccessToken());
|
|
||||||
redirectUrlSupplier = createRedirectUrlSupplier();
|
|
||||||
restartStreamingUpdates();
|
|
||||||
} catch (InvalidWWNAccessTokenException e) {
|
|
||||||
logger.debug("Invalid access token", e);
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
|
||||||
"Token is invalid and could not be refreshed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}, 0, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
logger.debug("Finished initializing Nest bridge handler");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clean up the handler.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void dispose() {
|
|
||||||
logger.debug("Nest bridge disposed");
|
|
||||||
stopStreamingUpdates();
|
|
||||||
|
|
||||||
ScheduledFuture<?> localInitializeJob = initializeJob;
|
|
||||||
if (localInitializeJob != null && !localInitializeJob.isCancelled()) {
|
|
||||||
localInitializeJob.cancel(true);
|
|
||||||
initializeJob = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduledFuture<?> localTransmitJob = transmitJob;
|
|
||||||
if (localTransmitJob != null && !localTransmitJob.isCancelled()) {
|
|
||||||
localTransmitJob.cancel(true);
|
|
||||||
transmitJob = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.authorizer = null;
|
|
||||||
this.redirectUrlSupplier = null;
|
|
||||||
this.streamingRestClient = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> boolean addThingDataListener(Class<T> dataClass, WWNThingDataListener<T> listener) {
|
|
||||||
return updateHandler.addListener(dataClass, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> boolean addThingDataListener(Class<T> dataClass, String nestId, WWNThingDataListener<T> listener) {
|
|
||||||
return updateHandler.addListener(dataClass, nestId, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the update request into the queue for doing something with, send immediately if the queue is empty.
|
|
||||||
*/
|
|
||||||
public void addUpdateRequest(WWNUpdateRequest request) {
|
|
||||||
nestUpdateRequests.add(request);
|
|
||||||
scheduleTransmitJobForPendingRequests();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected WWNRedirectUrlSupplier createRedirectUrlSupplier() throws InvalidWWNAccessTokenException {
|
|
||||||
return new WWNRedirectUrlSupplier(getHttpHeaders());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getExistingOrNewAccessToken() throws InvalidWWNAccessTokenException {
|
|
||||||
String accessToken = config.accessToken;
|
|
||||||
if (accessToken == null || accessToken.isEmpty()) {
|
|
||||||
accessToken = authorizer.getNewAccessToken();
|
|
||||||
config.accessToken = accessToken;
|
|
||||||
config.pincode = "";
|
|
||||||
// Update and save the access token in the bridge configuration
|
|
||||||
Configuration configuration = editConfiguration();
|
|
||||||
configuration.put(WWNAccountConfiguration.ACCESS_TOKEN, config.accessToken);
|
|
||||||
configuration.put(WWNAccountConfiguration.PINCODE, config.pincode);
|
|
||||||
updateConfiguration(configuration);
|
|
||||||
logger.debug("Retrieved new access token: {}", config.accessToken);
|
|
||||||
return accessToken;
|
|
||||||
} else {
|
|
||||||
logger.debug("Re-using access token from configuration: {}", accessToken);
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Properties getHttpHeaders() throws InvalidWWNAccessTokenException {
|
|
||||||
Properties httpHeaders = new Properties();
|
|
||||||
httpHeaders.put("Authorization", "Bearer " + getExistingOrNewAccessToken());
|
|
||||||
httpHeaders.put("Content-Type", JSON_CONTENT_TYPE);
|
|
||||||
return httpHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable <T> T getLastUpdate(Class<T> dataClass, String nestId) {
|
|
||||||
return updateHandler.getLastUpdate(dataClass, nestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> List<T> getLastUpdates(Class<T> dataClass) {
|
|
||||||
return updateHandler.getLastUpdates(dataClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
private WWNRedirectUrlSupplier getOrCreateRedirectUrlSupplier() throws InvalidWWNAccessTokenException {
|
|
||||||
WWNRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier;
|
|
||||||
if (localRedirectUrlSupplier == null) {
|
|
||||||
localRedirectUrlSupplier = createRedirectUrlSupplier();
|
|
||||||
redirectUrlSupplier = localRedirectUrlSupplier;
|
|
||||||
}
|
|
||||||
return localRedirectUrlSupplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> getPresentThingsNestIds() {
|
|
||||||
Set<String> nestIds = new HashSet<>();
|
|
||||||
for (Thing thing : getThing().getThings()) {
|
|
||||||
ThingHandler handler = thing.getHandler();
|
|
||||||
if (handler != null && thing.getStatusInfo().getStatusDetail() != ThingStatusDetail.GONE) {
|
|
||||||
nestIds.add(((WWNIdentifiable) handler).getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nestIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
|
||||||
return List.of(WWNDiscoveryService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles an incoming command update
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
|
||||||
if (command instanceof RefreshType) {
|
|
||||||
logger.debug("Refresh command received");
|
|
||||||
updateHandler.resendLastUpdates();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void jsonToPutUrl(WWNUpdateRequest request)
|
|
||||||
throws FailedSendingWWNDataException, InvalidWWNAccessTokenException, FailedResolvingWWNUrlException {
|
|
||||||
try {
|
|
||||||
WWNRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier;
|
|
||||||
if (localRedirectUrlSupplier == null) {
|
|
||||||
throw new FailedResolvingWWNUrlException("redirectUrlSupplier is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
String url = localRedirectUrlSupplier.getRedirectUrl() + request.getUpdatePath();
|
|
||||||
logger.debug("Putting data to: {}", url);
|
|
||||||
|
|
||||||
String jsonContent = WWNUtils.toJson(request.getValues());
|
|
||||||
logger.debug("PUT content: {}", jsonContent);
|
|
||||||
|
|
||||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8));
|
|
||||||
String jsonResponse = HttpUtil.executeUrl("PUT", url, getHttpHeaders(), inputStream, JSON_CONTENT_TYPE,
|
|
||||||
REQUEST_TIMEOUT);
|
|
||||||
logger.debug("PUT response: {}", jsonResponse);
|
|
||||||
|
|
||||||
WWNErrorData error = WWNUtils.fromJson(jsonResponse, WWNErrorData.class);
|
|
||||||
if (error.getError() != null && !error.getError().isBlank()) {
|
|
||||||
logger.debug("Nest API error: {}", error);
|
|
||||||
logger.warn("Nest API error: {}", error.getMessage());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new FailedSendingWWNDataException("Failed to send data", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthorizationRevoked(String token) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
|
||||||
"Authorization token revoked: " + token);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnected() {
|
|
||||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Streaming data connection established");
|
|
||||||
scheduleTransmitJobForPendingRequests();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisconnected() {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Streaming data disconnected");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(String message) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNewTopLevelData(WWNTopLevelData data) {
|
|
||||||
updateHandler.handleUpdate(data);
|
|
||||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Receiving streaming data");
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> boolean removeThingDataListener(Class<T> dataClass, WWNThingDataListener<T> listener) {
|
|
||||||
return updateHandler.removeListener(dataClass, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> boolean removeThingDataListener(Class<T> dataClass, String nestId, WWNThingDataListener<T> listener) {
|
|
||||||
return updateHandler.removeListener(dataClass, nestId, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restartStreamingUpdates() {
|
|
||||||
synchronized (this) {
|
|
||||||
stopStreamingUpdates();
|
|
||||||
startStreamingUpdates();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scheduleTransmitJobForPendingRequests() {
|
|
||||||
ScheduledFuture<?> localTransmitJob = transmitJob;
|
|
||||||
if (!nestUpdateRequests.isEmpty() && (localTransmitJob == null || localTransmitJob.isDone())) {
|
|
||||||
transmitJob = scheduler.schedule(this::transmitQueue, 0, SECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startStreamingUpdates() {
|
|
||||||
synchronized (this) {
|
|
||||||
try {
|
|
||||||
WWNStreamingRestClient localStreamingRestClient = new WWNStreamingRestClient(
|
|
||||||
getExistingOrNewAccessToken(), clientBuilder, eventSourceFactory,
|
|
||||||
getOrCreateRedirectUrlSupplier(), scheduler);
|
|
||||||
localStreamingRestClient.addStreamingDataListener(this);
|
|
||||||
localStreamingRestClient.start();
|
|
||||||
|
|
||||||
streamingRestClient = localStreamingRestClient;
|
|
||||||
} catch (InvalidWWNAccessTokenException e) {
|
|
||||||
logger.debug("Invalid access token", e);
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
|
||||||
"Token is invalid and could not be refreshed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopStreamingUpdates() {
|
|
||||||
WWNStreamingRestClient localStreamingRestClient = streamingRestClient;
|
|
||||||
if (localStreamingRestClient != null) {
|
|
||||||
synchronized (this) {
|
|
||||||
localStreamingRestClient.stop();
|
|
||||||
localStreamingRestClient.removeStreamingDataListener(this);
|
|
||||||
streamingRestClient = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void transmitQueue() {
|
|
||||||
if (getThing().getStatus() == ThingStatus.OFFLINE) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
|
||||||
"Not transmitting events because bridge is OFFLINE");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (!nestUpdateRequests.isEmpty()) {
|
|
||||||
// nestUpdateRequests is a CopyOnWriteArrayList so its iterator does not support remove operations
|
|
||||||
WWNUpdateRequest request = nestUpdateRequests.get(0);
|
|
||||||
jsonToPutUrl(request);
|
|
||||||
nestUpdateRequests.remove(request);
|
|
||||||
}
|
|
||||||
} catch (InvalidWWNAccessTokenException e) {
|
|
||||||
logger.debug("Invalid access token", e);
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
|
||||||
"Token is invalid and could not be refreshed: " + e.getMessage());
|
|
||||||
} catch (FailedResolvingWWNUrlException e) {
|
|
||||||
logger.debug("Unable to resolve redirect URL", e);
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
|
||||||
scheduler.schedule(this::restartStreamingUpdates, 5, SECONDS);
|
|
||||||
} catch (FailedSendingWWNDataException e) {
|
|
||||||
logger.debug("Error sending data", e);
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
|
||||||
scheduler.schedule(this::restartStreamingUpdates, 5, SECONDS);
|
|
||||||
|
|
||||||
WWNRedirectUrlSupplier localRedirectUrlSupplier = redirectUrlSupplier;
|
|
||||||
if (localRedirectUrlSupplier != null) {
|
|
||||||
localRedirectUrlSupplier.resetCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,204 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.measure.Quantity;
|
|
||||||
import javax.measure.Unit;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNull;
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNIdentifiable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNUpdateRequest;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.listener.WWNThingDataListener;
|
|
||||||
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.StringType;
|
|
||||||
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.types.State;
|
|
||||||
import org.openhab.core.types.UnDefType;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deals with the structures on the WWN API, turning them into a thing in openHAB.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Martin van Wingerden - Splitted of NestBaseHandler
|
|
||||||
* @author Wouter Born - Add generic update data type
|
|
||||||
*
|
|
||||||
* @param <T> the type of update data
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public abstract class WWNBaseHandler<@NonNull T> extends BaseThingHandler
|
|
||||||
implements WWNThingDataListener<T>, WWNIdentifiable {
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNBaseHandler.class);
|
|
||||||
|
|
||||||
private String deviceId = "";
|
|
||||||
private Class<T> dataClass;
|
|
||||||
|
|
||||||
WWNBaseHandler(Thing thing, Class<T> dataClass) {
|
|
||||||
super(thing);
|
|
||||||
this.dataClass = dataClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
logger.debug("Initializing handler for {}", getClass().getName());
|
|
||||||
|
|
||||||
WWNAccountHandler handler = getAccountHandler();
|
|
||||||
if (handler != null) {
|
|
||||||
boolean success = handler.addThingDataListener(dataClass, getId(), this);
|
|
||||||
logger.debug("Adding {} with ID '{}' as device data listener, result: {}", getClass().getSimpleName(),
|
|
||||||
getId(), success);
|
|
||||||
} else {
|
|
||||||
logger.debug("Unable to add {} with ID '{}' as device data listener because bridge is null",
|
|
||||||
getClass().getSimpleName(), getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Waiting for refresh");
|
|
||||||
|
|
||||||
final @Nullable T lastUpdate = getLastUpdate();
|
|
||||||
if (lastUpdate != null) {
|
|
||||||
update(null, lastUpdate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose() {
|
|
||||||
WWNAccountHandler handler = getAccountHandler();
|
|
||||||
if (handler != null) {
|
|
||||||
handler.removeThingDataListener(dataClass, getId(), this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected @Nullable T getLastUpdate() {
|
|
||||||
WWNAccountHandler handler = getAccountHandler();
|
|
||||||
if (handler != null) {
|
|
||||||
return handler.getLastUpdate(dataClass, getId());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addUpdateRequest(String updatePath, String field, Object value) {
|
|
||||||
WWNAccountHandler handler = getAccountHandler();
|
|
||||||
if (handler != null) {
|
|
||||||
handler.addUpdateRequest(new WWNUpdateRequest.Builder() //
|
|
||||||
.withBasePath(updatePath) //
|
|
||||||
.withIdentifier(getId()) //
|
|
||||||
.withAdditionalValue(field, value) //
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return getDeviceId();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getDeviceId() {
|
|
||||||
String localDeviceId = deviceId;
|
|
||||||
if (localDeviceId.isEmpty()) {
|
|
||||||
localDeviceId = getConfigAs(WWNDeviceConfiguration.class).deviceId;
|
|
||||||
deviceId = localDeviceId;
|
|
||||||
}
|
|
||||||
return localDeviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected @Nullable WWNAccountHandler getAccountHandler() {
|
|
||||||
Bridge bridge = getBridge();
|
|
||||||
return bridge != null ? (WWNAccountHandler) bridge.getHandler() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract State getChannelState(ChannelUID channelUID, T data);
|
|
||||||
|
|
||||||
protected State getAsDateTimeTypeOrNull(@Nullable Date date) {
|
|
||||||
if (date == null) {
|
|
||||||
return UnDefType.NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
long offsetMillis = TimeZone.getDefault().getOffset(date.getTime());
|
|
||||||
Instant instant = date.toInstant().plusMillis(offsetMillis);
|
|
||||||
return new DateTimeType(ZonedDateTime.ofInstant(instant, TimeZone.getDefault().toZoneId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected State getAsDecimalTypeOrNull(@Nullable Integer value) {
|
|
||||||
return value == null ? UnDefType.NULL : new DecimalType(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected State getAsOnOffTypeOrNull(@Nullable Boolean value) {
|
|
||||||
return value == null ? UnDefType.NULL : value ? OnOffType.ON : OnOffType.OFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <U extends Quantity<U>> State getAsQuantityTypeOrNull(@Nullable Number value, Unit<U> unit) {
|
|
||||||
return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected State getAsStringTypeOrNull(@Nullable Object value) {
|
|
||||||
return value == null ? UnDefType.NULL : new StringType(value.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected State getAsStringTypeListOrNull(@Nullable Collection<@NonNull ?> values) {
|
|
||||||
return values == null || values.isEmpty() ? UnDefType.NULL
|
|
||||||
: new StringType(values.stream().map(value -> value.toString()).collect(Collectors.joining(",")));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isNotHandling(WWNIdentifiable nestIdentifiable) {
|
|
||||||
return !(getId().equals(nestIdentifiable.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateLinkedChannels(@Nullable T oldData, T data) {
|
|
||||||
getThing().getChannels().stream().map(channel -> channel.getUID()).filter(this::isLinked)
|
|
||||||
.forEach(channelUID -> {
|
|
||||||
State newState = getChannelState(channelUID, data);
|
|
||||||
if (oldData == null || !getChannelState(channelUID, oldData).equals(newState)) {
|
|
||||||
logger.debug("Updating {}", channelUID);
|
|
||||||
updateState(channelUID, newState);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNewData(T data) {
|
|
||||||
update(null, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpdatedData(T oldData, T data) {
|
|
||||||
update(oldData, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMissingData(String nestId) {
|
|
||||||
thing.setStatusInfo(
|
|
||||||
new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Missing from streaming updates"));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void update(@Nullable T oldData, T data);
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION;
|
|
||||||
import static org.openhab.core.types.RefreshType.REFRESH;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNCamera;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNCameraEvent;
|
|
||||||
import org.openhab.core.library.types.OnOffType;
|
|
||||||
import org.openhab.core.thing.ChannelUID;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.openhab.core.thing.ThingStatus;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles all the updates to the camera as well as handling the commands that send updates to the WWN API.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Handle channel refresh command
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNCameraHandler extends WWNBaseHandler<WWNCamera> {
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNCameraHandler.class);
|
|
||||||
|
|
||||||
public WWNCameraHandler(Thing thing) {
|
|
||||||
super(thing, WWNCamera.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected State getChannelState(ChannelUID channelUID, WWNCamera camera) {
|
|
||||||
if (channelUID.getId().startsWith(CHANNEL_GROUP_CAMERA_PREFIX)) {
|
|
||||||
return getCameraChannelState(channelUID, camera);
|
|
||||||
} else if (channelUID.getId().startsWith(CHANNEL_GROUP_LAST_EVENT_PREFIX)) {
|
|
||||||
return getLastEventChannelState(channelUID, camera);
|
|
||||||
} else {
|
|
||||||
logger.error("Unsupported channelId '{}'", channelUID.getId());
|
|
||||||
return UnDefType.UNDEF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected State getCameraChannelState(ChannelUID channelUID, WWNCamera camera) {
|
|
||||||
switch (channelUID.getId()) {
|
|
||||||
case CHANNEL_CAMERA_APP_URL:
|
|
||||||
return getAsStringTypeOrNull(camera.getAppUrl());
|
|
||||||
case CHANNEL_CAMERA_AUDIO_INPUT_ENABLED:
|
|
||||||
return getAsOnOffTypeOrNull(camera.isAudioInputEnabled());
|
|
||||||
case CHANNEL_CAMERA_LAST_ONLINE_CHANGE:
|
|
||||||
return getAsDateTimeTypeOrNull(camera.getLastIsOnlineChange());
|
|
||||||
case CHANNEL_CAMERA_PUBLIC_SHARE_ENABLED:
|
|
||||||
return getAsOnOffTypeOrNull(camera.isPublicShareEnabled());
|
|
||||||
case CHANNEL_CAMERA_PUBLIC_SHARE_URL:
|
|
||||||
return getAsStringTypeOrNull(camera.getPublicShareUrl());
|
|
||||||
case CHANNEL_CAMERA_SNAPSHOT_URL:
|
|
||||||
return getAsStringTypeOrNull(camera.getSnapshotUrl());
|
|
||||||
case CHANNEL_CAMERA_STREAMING:
|
|
||||||
return getAsOnOffTypeOrNull(camera.isStreaming());
|
|
||||||
case CHANNEL_CAMERA_VIDEO_HISTORY_ENABLED:
|
|
||||||
return getAsOnOffTypeOrNull(camera.isVideoHistoryEnabled());
|
|
||||||
case CHANNEL_CAMERA_WEB_URL:
|
|
||||||
return getAsStringTypeOrNull(camera.getWebUrl());
|
|
||||||
default:
|
|
||||||
logger.error("Unsupported channelId '{}'", channelUID.getId());
|
|
||||||
return UnDefType.UNDEF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected State getLastEventChannelState(ChannelUID channelUID, WWNCamera camera) {
|
|
||||||
WWNCameraEvent lastEvent = camera.getLastEvent();
|
|
||||||
if (lastEvent == null) {
|
|
||||||
return UnDefType.NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (channelUID.getId()) {
|
|
||||||
case CHANNEL_LAST_EVENT_ACTIVITY_ZONES:
|
|
||||||
return getAsStringTypeListOrNull(lastEvent.getActivityZones());
|
|
||||||
case CHANNEL_LAST_EVENT_ANIMATED_IMAGE_URL:
|
|
||||||
return getAsStringTypeOrNull(lastEvent.getAnimatedImageUrl());
|
|
||||||
case CHANNEL_LAST_EVENT_APP_URL:
|
|
||||||
return getAsStringTypeOrNull(lastEvent.getAppUrl());
|
|
||||||
case CHANNEL_LAST_EVENT_END_TIME:
|
|
||||||
return getAsDateTimeTypeOrNull(lastEvent.getEndTime());
|
|
||||||
case CHANNEL_LAST_EVENT_HAS_MOTION:
|
|
||||||
return getAsOnOffTypeOrNull(lastEvent.isHasMotion());
|
|
||||||
case CHANNEL_LAST_EVENT_HAS_PERSON:
|
|
||||||
return getAsOnOffTypeOrNull(lastEvent.isHasPerson());
|
|
||||||
case CHANNEL_LAST_EVENT_HAS_SOUND:
|
|
||||||
return getAsOnOffTypeOrNull(lastEvent.isHasSound());
|
|
||||||
case CHANNEL_LAST_EVENT_IMAGE_URL:
|
|
||||||
return getAsStringTypeOrNull(lastEvent.getImageUrl());
|
|
||||||
case CHANNEL_LAST_EVENT_START_TIME:
|
|
||||||
return getAsDateTimeTypeOrNull(lastEvent.getStartTime());
|
|
||||||
case CHANNEL_LAST_EVENT_URLS_EXPIRE_TIME:
|
|
||||||
return getAsDateTimeTypeOrNull(lastEvent.getUrlsExpireTime());
|
|
||||||
case CHANNEL_LAST_EVENT_WEB_URL:
|
|
||||||
return getAsStringTypeOrNull(lastEvent.getWebUrl());
|
|
||||||
default:
|
|
||||||
logger.error("Unsupported channelId '{}'", channelUID.getId());
|
|
||||||
return UnDefType.UNDEF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
|
||||||
if (REFRESH.equals(command)) {
|
|
||||||
WWNCamera lastUpdate = getLastUpdate();
|
|
||||||
if (lastUpdate != null) {
|
|
||||||
updateState(channelUID, getChannelState(channelUID, lastUpdate));
|
|
||||||
}
|
|
||||||
} else if (CHANNEL_CAMERA_STREAMING.equals(channelUID.getId())) {
|
|
||||||
// Change the mode.
|
|
||||||
if (command instanceof OnOffType) {
|
|
||||||
// Set the mode to be the cmd value.
|
|
||||||
addUpdateRequest("is_streaming", command == OnOffType.ON);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addUpdateRequest(String field, Object value) {
|
|
||||||
addUpdateRequest(NEST_CAMERA_UPDATE_PATH, field, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void update(@Nullable WWNCamera oldCamera, WWNCamera camera) {
|
|
||||||
logger.debug("Updating {}", getThing().getUID());
|
|
||||||
|
|
||||||
updateLinkedChannels(oldCamera, camera);
|
|
||||||
updateProperty(PROPERTY_FIRMWARE_VERSION, camera.getSoftwareVersion());
|
|
||||||
|
|
||||||
ThingStatus newStatus = camera.isOnline() == null ? ThingStatus.UNKNOWN
|
|
||||||
: camera.isOnline() ? ThingStatus.ONLINE : ThingStatus.OFFLINE;
|
|
||||||
if (newStatus != thing.getStatus()) {
|
|
||||||
updateStatus(newStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
|
||||||
import org.eclipse.jetty.client.api.Request;
|
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.WWNBindingConstants;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.exceptions.FailedResolvingWWNUrlException;
|
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supplies resolved redirect URLs of {@link WWNBindingConstants#NEST_URL} so they can be used with HTTP clients that
|
|
||||||
* do not pass Authorization headers after redirects like the Jetty client used by {@link HttpUtil}.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Extract resolving redirect URL from NestBridgeHandler into NestRedirectUrlSupplier
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNRedirectUrlSupplier {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNRedirectUrlSupplier.class);
|
|
||||||
|
|
||||||
protected String cachedUrl = "";
|
|
||||||
|
|
||||||
protected Properties httpHeaders;
|
|
||||||
|
|
||||||
public WWNRedirectUrlSupplier(Properties httpHeaders) {
|
|
||||||
this.httpHeaders = httpHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRedirectUrl() throws FailedResolvingWWNUrlException {
|
|
||||||
if (cachedUrl.isEmpty()) {
|
|
||||||
cachedUrl = resolveRedirectUrl();
|
|
||||||
}
|
|
||||||
return cachedUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetCache() {
|
|
||||||
cachedUrl = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the redirect URL for calls using the {@link WWNBindingConstants#NEST_URL}.
|
|
||||||
*
|
|
||||||
* The Jetty client used by {@link HttpUtil} will not pass the Authorization header after a redirect resulting in
|
|
||||||
* "401 Unauthorized error" issues.
|
|
||||||
*
|
|
||||||
* Note that this workaround currently does not use any configured proxy like {@link HttpUtil} does.
|
|
||||||
*
|
|
||||||
* @see https://developers.nest.com/documentation/cloud/how-to-handle-redirects
|
|
||||||
*/
|
|
||||||
private String resolveRedirectUrl() throws FailedResolvingWWNUrlException {
|
|
||||||
HttpClient httpClient = new HttpClient(new SslContextFactory.Client());
|
|
||||||
httpClient.setFollowRedirects(false);
|
|
||||||
|
|
||||||
Request request = httpClient.newRequest(WWNBindingConstants.NEST_URL).method(HttpMethod.GET).timeout(30,
|
|
||||||
TimeUnit.SECONDS);
|
|
||||||
for (String httpHeaderKey : httpHeaders.stringPropertyNames()) {
|
|
||||||
request.header(httpHeaderKey, httpHeaders.getProperty(httpHeaderKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentResponse response;
|
|
||||||
try {
|
|
||||||
httpClient.start();
|
|
||||||
response = request.send();
|
|
||||||
httpClient.stop();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new FailedResolvingWWNUrlException("Failed to resolve redirect URL: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
int status = response.getStatus();
|
|
||||||
String redirectUrl = response.getHeaders().get(HttpHeader.LOCATION);
|
|
||||||
|
|
||||||
if (status != HttpStatus.TEMPORARY_REDIRECT_307) {
|
|
||||||
logger.debug("Redirect status: {}", status);
|
|
||||||
logger.debug("Redirect response: {}", response.getContentAsString());
|
|
||||||
throw new FailedResolvingWWNUrlException("Failed to get redirect URL, expected status "
|
|
||||||
+ HttpStatus.TEMPORARY_REDIRECT_307 + " but was " + status);
|
|
||||||
} else if (redirectUrl == null || redirectUrl.isEmpty()) {
|
|
||||||
throw new FailedResolvingWWNUrlException("Redirect URL is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectUrl = redirectUrl.endsWith("/") ? redirectUrl.substring(0, redirectUrl.length() - 1) : redirectUrl;
|
|
||||||
logger.debug("Redirect URL: {}", redirectUrl);
|
|
||||||
return redirectUrl;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION;
|
|
||||||
import static org.openhab.core.types.RefreshType.REFRESH;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNSmokeDetector;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNSmokeDetector.BatteryHealth;
|
|
||||||
import org.openhab.core.thing.ChannelUID;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.openhab.core.thing.ThingStatus;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The smoke detector handler, it handles the data from WWN for the smoke detector.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Handle channel refresh command
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNSmokeDetectorHandler extends WWNBaseHandler<WWNSmokeDetector> {
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNSmokeDetectorHandler.class);
|
|
||||||
|
|
||||||
public WWNSmokeDetectorHandler(Thing thing) {
|
|
||||||
super(thing, WWNSmokeDetector.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected State getChannelState(ChannelUID channelUID, WWNSmokeDetector smokeDetector) {
|
|
||||||
switch (channelUID.getId()) {
|
|
||||||
case CHANNEL_CO_ALARM_STATE:
|
|
||||||
return getAsStringTypeOrNull(smokeDetector.getCoAlarmState());
|
|
||||||
case CHANNEL_LAST_CONNECTION:
|
|
||||||
return getAsDateTimeTypeOrNull(smokeDetector.getLastConnection());
|
|
||||||
case CHANNEL_LAST_MANUAL_TEST_TIME:
|
|
||||||
return getAsDateTimeTypeOrNull(smokeDetector.getLastManualTestTime());
|
|
||||||
case CHANNEL_LOW_BATTERY:
|
|
||||||
return getAsOnOffTypeOrNull(smokeDetector.getBatteryHealth() == null ? null
|
|
||||||
: smokeDetector.getBatteryHealth() == BatteryHealth.REPLACE);
|
|
||||||
case CHANNEL_MANUAL_TEST_ACTIVE:
|
|
||||||
return getAsOnOffTypeOrNull(smokeDetector.isManualTestActive());
|
|
||||||
case CHANNEL_SMOKE_ALARM_STATE:
|
|
||||||
return getAsStringTypeOrNull(smokeDetector.getSmokeAlarmState());
|
|
||||||
case CHANNEL_UI_COLOR_STATE:
|
|
||||||
return getAsStringTypeOrNull(smokeDetector.getUiColorState());
|
|
||||||
default:
|
|
||||||
logger.error("Unsupported channelId '{}'", channelUID.getId());
|
|
||||||
return UnDefType.UNDEF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles any incoming command requests.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
|
||||||
if (REFRESH.equals(command)) {
|
|
||||||
WWNSmokeDetector lastUpdate = getLastUpdate();
|
|
||||||
if (lastUpdate != null) {
|
|
||||||
updateState(channelUID, getChannelState(channelUID, lastUpdate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void update(@Nullable WWNSmokeDetector oldSmokeDetector, WWNSmokeDetector smokeDetector) {
|
|
||||||
logger.debug("Updating {}", getThing().getUID());
|
|
||||||
|
|
||||||
updateLinkedChannels(oldSmokeDetector, smokeDetector);
|
|
||||||
updateProperty(PROPERTY_FIRMWARE_VERSION, smokeDetector.getSoftwareVersion());
|
|
||||||
|
|
||||||
ThingStatus newStatus = smokeDetector.isOnline() == null ? ThingStatus.UNKNOWN
|
|
||||||
: smokeDetector.isOnline() ? ThingStatus.ONLINE : ThingStatus.OFFLINE;
|
|
||||||
if (newStatus != thing.getStatus()) {
|
|
||||||
updateStatus(newStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
import static org.openhab.core.types.RefreshType.REFRESH;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNStructureConfiguration;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNStructure;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNStructure.HomeAwayState;
|
|
||||||
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.types.Command;
|
|
||||||
import org.openhab.core.types.State;
|
|
||||||
import org.openhab.core.types.UnDefType;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deals with the structures on the WWN API, turning them into a thing in openHAB.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Handle channel refresh command
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNStructureHandler extends WWNBaseHandler<WWNStructure> {
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNStructureHandler.class);
|
|
||||||
|
|
||||||
private @Nullable String structureId;
|
|
||||||
|
|
||||||
public WWNStructureHandler(Thing thing) {
|
|
||||||
super(thing, WWNStructure.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected State getChannelState(ChannelUID channelUID, WWNStructure structure) {
|
|
||||||
switch (channelUID.getId()) {
|
|
||||||
case CHANNEL_AWAY:
|
|
||||||
return getAsStringTypeOrNull(structure.getAway());
|
|
||||||
case CHANNEL_CO_ALARM_STATE:
|
|
||||||
return getAsStringTypeOrNull(structure.getCoAlarmState());
|
|
||||||
case CHANNEL_COUNTRY_CODE:
|
|
||||||
return getAsStringTypeOrNull(structure.getCountryCode());
|
|
||||||
case CHANNEL_ETA_BEGIN:
|
|
||||||
return getAsDateTimeTypeOrNull(structure.getEtaBegin());
|
|
||||||
case CHANNEL_PEAK_PERIOD_END_TIME:
|
|
||||||
return getAsDateTimeTypeOrNull(structure.getPeakPeriodEndTime());
|
|
||||||
case CHANNEL_PEAK_PERIOD_START_TIME:
|
|
||||||
return getAsDateTimeTypeOrNull(structure.getPeakPeriodStartTime());
|
|
||||||
case CHANNEL_POSTAL_CODE:
|
|
||||||
return getAsStringTypeOrNull(structure.getPostalCode());
|
|
||||||
case CHANNEL_RUSH_HOUR_REWARDS_ENROLLMENT:
|
|
||||||
return getAsOnOffTypeOrNull(structure.isRhrEnrollment());
|
|
||||||
case CHANNEL_SECURITY_STATE:
|
|
||||||
return getAsStringTypeOrNull(structure.getWwnSecurityState());
|
|
||||||
case CHANNEL_SMOKE_ALARM_STATE:
|
|
||||||
return getAsStringTypeOrNull(structure.getSmokeAlarmState());
|
|
||||||
case CHANNEL_TIME_ZONE:
|
|
||||||
return getAsStringTypeOrNull(structure.getTimeZone());
|
|
||||||
default:
|
|
||||||
logger.error("Unsupported channelId '{}'", channelUID.getId());
|
|
||||||
return UnDefType.UNDEF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return getStructureId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getStructureId() {
|
|
||||||
String localStructureId = structureId;
|
|
||||||
if (localStructureId == null) {
|
|
||||||
localStructureId = getConfigAs(WWNStructureConfiguration.class).structureId;
|
|
||||||
structureId = localStructureId;
|
|
||||||
}
|
|
||||||
return localStructureId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles updating the details on this structure by sending the request all the way
|
|
||||||
* to Nest.
|
|
||||||
*
|
|
||||||
* @param channelUID the channel to update
|
|
||||||
* @param command the command to apply
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
|
||||||
if (REFRESH.equals(command)) {
|
|
||||||
WWNStructure lastUpdate = getLastUpdate();
|
|
||||||
if (lastUpdate != null) {
|
|
||||||
updateState(channelUID, getChannelState(channelUID, lastUpdate));
|
|
||||||
}
|
|
||||||
} else if (CHANNEL_AWAY.equals(channelUID.getId())) {
|
|
||||||
// Change the home/away state.
|
|
||||||
if (command instanceof StringType) {
|
|
||||||
StringType cmd = (StringType) command;
|
|
||||||
// Set the mode to be the cmd value.
|
|
||||||
addUpdateRequest(NEST_STRUCTURE_UPDATE_PATH, "away", HomeAwayState.valueOf(cmd.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void update(@Nullable WWNStructure oldStructure, WWNStructure structure) {
|
|
||||||
logger.debug("Updating {}", getThing().getUID());
|
|
||||||
|
|
||||||
updateLinkedChannels(oldStructure, structure);
|
|
||||||
|
|
||||||
if (ThingStatus.ONLINE != thing.getStatus()) {
|
|
||||||
updateStatus(ThingStatus.ONLINE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,219 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
|
||||||
import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION;
|
|
||||||
import static org.openhab.core.types.RefreshType.REFRESH;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.RoundingMode;
|
|
||||||
|
|
||||||
import javax.measure.Unit;
|
|
||||||
import javax.measure.quantity.Temperature;
|
|
||||||
import javax.measure.quantity.Time;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNThermostat;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNThermostat.Mode;
|
|
||||||
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.library.unit.Units;
|
|
||||||
import org.openhab.core.thing.ChannelUID;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.openhab.core.thing.ThingStatus;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link WWNThermostatHandler} is responsible for handling commands, which are
|
|
||||||
* sent to one of the channels for the thermostat.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Handle channel refresh command
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNThermostatHandler extends WWNBaseHandler<WWNThermostat> {
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNThermostatHandler.class);
|
|
||||||
|
|
||||||
public WWNThermostatHandler(Thing thing) {
|
|
||||||
super(thing, WWNThermostat.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected State getChannelState(ChannelUID channelUID, WWNThermostat thermostat) {
|
|
||||||
switch (channelUID.getId()) {
|
|
||||||
case CHANNEL_CAN_COOL:
|
|
||||||
return getAsOnOffTypeOrNull(thermostat.isCanCool());
|
|
||||||
case CHANNEL_CAN_HEAT:
|
|
||||||
return getAsOnOffTypeOrNull(thermostat.isCanHeat());
|
|
||||||
case CHANNEL_ECO_MAX_SET_POINT:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getEcoTemperatureHigh(), thermostat.getTemperatureUnit());
|
|
||||||
case CHANNEL_ECO_MIN_SET_POINT:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getEcoTemperatureLow(), thermostat.getTemperatureUnit());
|
|
||||||
case CHANNEL_FAN_TIMER_ACTIVE:
|
|
||||||
return getAsOnOffTypeOrNull(thermostat.isFanTimerActive());
|
|
||||||
case CHANNEL_FAN_TIMER_DURATION:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getFanTimerDuration(), Units.MINUTE);
|
|
||||||
case CHANNEL_FAN_TIMER_TIMEOUT:
|
|
||||||
return getAsDateTimeTypeOrNull(thermostat.getFanTimerTimeout());
|
|
||||||
case CHANNEL_HAS_FAN:
|
|
||||||
return getAsOnOffTypeOrNull(thermostat.isHasFan());
|
|
||||||
case CHANNEL_HAS_LEAF:
|
|
||||||
return getAsOnOffTypeOrNull(thermostat.isHasLeaf());
|
|
||||||
case CHANNEL_HUMIDITY:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getHumidity(), Units.PERCENT);
|
|
||||||
case CHANNEL_LAST_CONNECTION:
|
|
||||||
return getAsDateTimeTypeOrNull(thermostat.getLastConnection());
|
|
||||||
case CHANNEL_LOCKED:
|
|
||||||
return getAsOnOffTypeOrNull(thermostat.isLocked());
|
|
||||||
case CHANNEL_LOCKED_MAX_SET_POINT:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getLockedTempMax(), thermostat.getTemperatureUnit());
|
|
||||||
case CHANNEL_LOCKED_MIN_SET_POINT:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getLockedTempMin(), thermostat.getTemperatureUnit());
|
|
||||||
case CHANNEL_MAX_SET_POINT:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getTargetTemperatureHigh(), thermostat.getTemperatureUnit());
|
|
||||||
case CHANNEL_MIN_SET_POINT:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getTargetTemperatureLow(), thermostat.getTemperatureUnit());
|
|
||||||
case CHANNEL_MODE:
|
|
||||||
return getAsStringTypeOrNull(thermostat.getMode());
|
|
||||||
case CHANNEL_PREVIOUS_MODE:
|
|
||||||
Mode previousMode = thermostat.getPreviousHvacMode() != null ? thermostat.getPreviousHvacMode()
|
|
||||||
: thermostat.getMode();
|
|
||||||
return getAsStringTypeOrNull(previousMode);
|
|
||||||
case CHANNEL_STATE:
|
|
||||||
return getAsStringTypeOrNull(thermostat.getHvacState());
|
|
||||||
case CHANNEL_SET_POINT:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getTargetTemperature(), thermostat.getTemperatureUnit());
|
|
||||||
case CHANNEL_SUNLIGHT_CORRECTION_ACTIVE:
|
|
||||||
return getAsOnOffTypeOrNull(thermostat.isSunlightCorrectionActive());
|
|
||||||
case CHANNEL_SUNLIGHT_CORRECTION_ENABLED:
|
|
||||||
return getAsOnOffTypeOrNull(thermostat.isSunlightCorrectionEnabled());
|
|
||||||
case CHANNEL_TEMPERATURE:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getAmbientTemperature(), thermostat.getTemperatureUnit());
|
|
||||||
case CHANNEL_TIME_TO_TARGET:
|
|
||||||
return getAsQuantityTypeOrNull(thermostat.getTimeToTarget(), Units.MINUTE);
|
|
||||||
case CHANNEL_USING_EMERGENCY_HEAT:
|
|
||||||
return getAsOnOffTypeOrNull(thermostat.isUsingEmergencyHeat());
|
|
||||||
default:
|
|
||||||
logger.error("Unsupported channelId '{}'", channelUID.getId());
|
|
||||||
return UnDefType.UNDEF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the command to do things to the thermostat, this will change the
|
|
||||||
* value of a channel by sending the request to Nest.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
|
||||||
if (REFRESH.equals(command)) {
|
|
||||||
WWNThermostat lastUpdate = getLastUpdate();
|
|
||||||
if (lastUpdate != null) {
|
|
||||||
updateState(channelUID, getChannelState(channelUID, lastUpdate));
|
|
||||||
}
|
|
||||||
} else if (CHANNEL_FAN_TIMER_ACTIVE.equals(channelUID.getId())) {
|
|
||||||
if (command instanceof OnOffType) {
|
|
||||||
// Update fan timer active to the command value
|
|
||||||
addUpdateRequest("fan_timer_active", command == OnOffType.ON);
|
|
||||||
}
|
|
||||||
} else if (CHANNEL_FAN_TIMER_DURATION.equals(channelUID.getId())) {
|
|
||||||
if (command instanceof QuantityType) {
|
|
||||||
// Update fan timer duration to the command value
|
|
||||||
QuantityType<Time> minuteQuantity = ((QuantityType<Time>) command).toUnit(Units.MINUTE);
|
|
||||||
if (minuteQuantity != null) {
|
|
||||||
addUpdateRequest("fan_timer_duration", minuteQuantity.intValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (CHANNEL_MAX_SET_POINT.equals(channelUID.getId())) {
|
|
||||||
if (command instanceof QuantityType) {
|
|
||||||
// Update maximum set point to the command value
|
|
||||||
addTemperatureUpdateRequest("target_temperature_high_c", "target_temperature_high_f",
|
|
||||||
(QuantityType<Temperature>) command);
|
|
||||||
}
|
|
||||||
} else if (CHANNEL_MIN_SET_POINT.equals(channelUID.getId())) {
|
|
||||||
if (command instanceof QuantityType) {
|
|
||||||
// Update minimum set point to the command value
|
|
||||||
addTemperatureUpdateRequest("target_temperature_low_c", "target_temperature_low_f",
|
|
||||||
(QuantityType<Temperature>) command);
|
|
||||||
}
|
|
||||||
} else if (CHANNEL_MODE.equals(channelUID.getId())) {
|
|
||||||
if (command instanceof StringType) {
|
|
||||||
// Update the HVAC mode to the command value
|
|
||||||
addUpdateRequest("hvac_mode", Mode.valueOf(((StringType) command).toString()));
|
|
||||||
}
|
|
||||||
} else if (CHANNEL_SET_POINT.equals(channelUID.getId())) {
|
|
||||||
if (command instanceof QuantityType) {
|
|
||||||
// Update set point to the command value
|
|
||||||
addTemperatureUpdateRequest("target_temperature_c", "target_temperature_f",
|
|
||||||
(QuantityType<Temperature>) command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addUpdateRequest(String field, Object value) {
|
|
||||||
addUpdateRequest(NEST_THERMOSTAT_UPDATE_PATH, field, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addTemperatureUpdateRequest(String celsiusField, String fahrenheitField,
|
|
||||||
QuantityType<Temperature> quantity) {
|
|
||||||
Unit<Temperature> unit = getTemperatureUnit(quantity.getUnit());
|
|
||||||
BigDecimal value = quantityToRoundedTemperature(quantity, unit);
|
|
||||||
if (value != null) {
|
|
||||||
addUpdateRequest(NEST_THERMOSTAT_UPDATE_PATH, unit == CELSIUS ? celsiusField : fahrenheitField, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Unit<Temperature> getTemperatureUnit(Unit<Temperature> fallbackUnit) {
|
|
||||||
WWNThermostat lastUpdate = getLastUpdate();
|
|
||||||
if (lastUpdate != null && lastUpdate.getTemperatureUnit() != null) {
|
|
||||||
return lastUpdate.getTemperatureUnit();
|
|
||||||
}
|
|
||||||
|
|
||||||
return fallbackUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable BigDecimal quantityToRoundedTemperature(QuantityType<Temperature> quantity,
|
|
||||||
Unit<Temperature> unit) throws IllegalArgumentException {
|
|
||||||
QuantityType<Temperature> temparatureQuantity = quantity.toUnit(unit);
|
|
||||||
if (temparatureQuantity == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal value = temparatureQuantity.toBigDecimal();
|
|
||||||
BigDecimal increment = CELSIUS == unit ? new BigDecimal("0.5") : new BigDecimal("1");
|
|
||||||
BigDecimal divisor = value.divide(increment, 0, RoundingMode.HALF_UP);
|
|
||||||
return divisor.multiply(increment);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void update(@Nullable WWNThermostat oldThermostat, WWNThermostat thermostat) {
|
|
||||||
logger.debug("Updating {}", getThing().getUID());
|
|
||||||
|
|
||||||
updateLinkedChannels(oldThermostat, thermostat);
|
|
||||||
updateProperty(PROPERTY_FIRMWARE_VERSION, thermostat.getSoftwareVersion());
|
|
||||||
|
|
||||||
ThingStatus newStatus = thermostat.isOnline() == null ? ThingStatus.UNKNOWN
|
|
||||||
: thermostat.isOnline() ? ThingStatus.ONLINE : ThingStatus.OFFLINE;
|
|
||||||
if (newStatus != thing.getStatus()) {
|
|
||||||
updateStatus(newStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.listener;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNTopLevelData;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for listeners of events generated by the {@link WWNStreamingRestClient}.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Replace polling with REST streaming
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public interface WWNStreamingDataListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorization has been revoked for a token.
|
|
||||||
*/
|
|
||||||
void onAuthorizationRevoked(String token);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The client successfully established a connection.
|
|
||||||
*/
|
|
||||||
void onConnected();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The client was disconnected.
|
|
||||||
*/
|
|
||||||
void onDisconnected();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An error message was published.
|
|
||||||
*/
|
|
||||||
void onError(String message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial {@link WWNTopLevelData} or an update is sent.
|
|
||||||
*/
|
|
||||||
void onNewTopLevelData(WWNTopLevelData data);
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.listener;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to track incoming data for WWN things.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public interface WWNThingDataListener<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An initial value for the data was received or the value is send again due to a refresh.
|
|
||||||
*
|
|
||||||
* @param data the data
|
|
||||||
*/
|
|
||||||
void onNewData(T data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Existing data was updated to a new value.
|
|
||||||
*
|
|
||||||
* @param oldData the previous value
|
|
||||||
* @param data the current value
|
|
||||||
*/
|
|
||||||
void onUpdatedData(T oldData, T data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Nest thing which previously had data is missing. E.g. it was removed from the account.
|
|
||||||
*
|
|
||||||
* @param nestId identifies the Nest thing
|
|
||||||
*/
|
|
||||||
void onMissingData(String nestId);
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.rest;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.WWNBindingConstants;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.WWNUtils;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNAccountConfiguration;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNAccessTokenData;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.exceptions.InvalidWWNAccessTokenException;
|
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the WWN access token using the OAuth 2.0 protocol using pin-based authorization.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Improve exception handling
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNAuthorizer {
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNAuthorizer.class);
|
|
||||||
|
|
||||||
private final WWNAccountConfiguration config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the helper class for the Nest access token. Also creates the folder
|
|
||||||
* to put the access token data in if it does not already exist.
|
|
||||||
*
|
|
||||||
* @param config The configuration to use for the token
|
|
||||||
*/
|
|
||||||
public WWNAuthorizer(WWNAccountConfiguration config) {
|
|
||||||
this.config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current access token, refreshing if needed.
|
|
||||||
*
|
|
||||||
* @throws InvalidWWNAccessTokenException thrown when the access token is invalid and could not be refreshed
|
|
||||||
*/
|
|
||||||
public String getNewAccessToken() throws InvalidWWNAccessTokenException {
|
|
||||||
try {
|
|
||||||
String pincode = config.pincode;
|
|
||||||
if (pincode == null || pincode.isBlank()) {
|
|
||||||
throw new InvalidWWNAccessTokenException("Pincode is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder urlBuilder = new StringBuilder(WWNBindingConstants.NEST_ACCESS_TOKEN_URL) //
|
|
||||||
.append("?client_id=") //
|
|
||||||
.append(config.productId) //
|
|
||||||
.append("&client_secret=") //
|
|
||||||
.append(config.productSecret) //
|
|
||||||
.append("&code=") //
|
|
||||||
.append(pincode) //
|
|
||||||
.append("&grant_type=authorization_code");
|
|
||||||
|
|
||||||
logger.debug("Requesting access token from URL: {}", urlBuilder);
|
|
||||||
|
|
||||||
String responseContentAsString = HttpUtil.executeUrl("POST", urlBuilder.toString(), null, null,
|
|
||||||
"application/x-www-form-urlencoded", 10_000);
|
|
||||||
|
|
||||||
WWNAccessTokenData data = WWNUtils.fromJson(responseContentAsString, WWNAccessTokenData.class);
|
|
||||||
logger.debug("Received: {}", data);
|
|
||||||
|
|
||||||
String accessToken = data.getAccessToken();
|
|
||||||
if (accessToken == null || accessToken.isBlank()) {
|
|
||||||
throw new InvalidWWNAccessTokenException("Pincode to obtain access token is already used or invalid)");
|
|
||||||
}
|
|
||||||
return accessToken;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InvalidWWNAccessTokenException("Access token request failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.rest;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.ws.rs.client.ClientRequestContext;
|
|
||||||
import javax.ws.rs.client.ClientRequestFilter;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts Authorization and Cache-Control headers for requests on the streaming WWN REST API.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Replace polling with REST streaming
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNStreamingRequestFilter implements ClientRequestFilter {
|
|
||||||
private final String accessToken;
|
|
||||||
|
|
||||||
public WWNStreamingRequestFilter(String accessToken) {
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void filter(@Nullable ClientRequestContext requestContext) throws IOException {
|
|
||||||
if (requestContext != null) {
|
|
||||||
MultivaluedMap<String, Object> headers = requestContext.getHeaders();
|
|
||||||
headers.putSingle(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
|
|
||||||
headers.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,232 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.rest;
|
|
||||||
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.KEEP_ALIVE_MILLIS;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.ws.rs.client.Client;
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
import javax.ws.rs.sse.InboundSseEvent;
|
|
||||||
import javax.ws.rs.sse.SseEventSource;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.WWNUtils;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNTopLevelData;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNTopLevelStreamingData;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.exceptions.FailedResolvingWWNUrlException;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.handler.WWNRedirectUrlSupplier;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.listener.WWNStreamingDataListener;
|
|
||||||
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A client that generates events based on Nest streaming WWN REST API Server-Sent Events (SSE).
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
* @author Wouter Born - Replace polling with REST streaming
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNStreamingRestClient {
|
|
||||||
|
|
||||||
// Assume connection timeout when 2 keep alive message should have been received
|
|
||||||
private static final long CONNECTION_TIMEOUT_MILLIS = 2 * KEEP_ALIVE_MILLIS + KEEP_ALIVE_MILLIS / 2;
|
|
||||||
|
|
||||||
public static final String AUTH_REVOKED = "auth_revoked";
|
|
||||||
public static final String ERROR = "error";
|
|
||||||
public static final String KEEP_ALIVE = "keep-alive";
|
|
||||||
public static final String OPEN = "open";
|
|
||||||
public static final String PUT = "put";
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNStreamingRestClient.class);
|
|
||||||
|
|
||||||
private final String accessToken;
|
|
||||||
private final ClientBuilder clientBuilder;
|
|
||||||
private final SseEventSourceFactory eventSourceFactory;
|
|
||||||
private final WWNRedirectUrlSupplier redirectUrlSupplier;
|
|
||||||
private final ScheduledExecutorService scheduler;
|
|
||||||
|
|
||||||
private final Object startStopLock = new Object();
|
|
||||||
private final List<WWNStreamingDataListener> listeners = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
private @Nullable ScheduledFuture<?> checkConnectionJob;
|
|
||||||
private boolean connected;
|
|
||||||
private @Nullable SseEventSource eventSource;
|
|
||||||
private long lastEventTimestamp;
|
|
||||||
private @Nullable WWNTopLevelData lastReceivedTopLevelData;
|
|
||||||
|
|
||||||
public WWNStreamingRestClient(String accessToken, ClientBuilder clientBuilder,
|
|
||||||
SseEventSourceFactory eventSourceFactory, WWNRedirectUrlSupplier redirectUrlSupplier,
|
|
||||||
ScheduledExecutorService scheduler) {
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.clientBuilder = clientBuilder;
|
|
||||||
this.eventSourceFactory = eventSourceFactory;
|
|
||||||
this.redirectUrlSupplier = redirectUrlSupplier;
|
|
||||||
this.scheduler = scheduler;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SseEventSource createEventSource() throws FailedResolvingWWNUrlException {
|
|
||||||
Client client = clientBuilder.register(new WWNStreamingRequestFilter(accessToken)).build();
|
|
||||||
SseEventSource eventSource = eventSourceFactory.newSource(client.target(redirectUrlSupplier.getRedirectUrl()));
|
|
||||||
eventSource.register(this::onEvent, this::onError);
|
|
||||||
return eventSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkConnection() {
|
|
||||||
long millisSinceLastEvent = System.currentTimeMillis() - lastEventTimestamp;
|
|
||||||
if (millisSinceLastEvent > CONNECTION_TIMEOUT_MILLIS) {
|
|
||||||
logger.debug("Check: Disconnected from streaming events, millisSinceLastEvent={}", millisSinceLastEvent);
|
|
||||||
synchronized (startStopLock) {
|
|
||||||
stopCheckConnectionJob(false);
|
|
||||||
if (connected) {
|
|
||||||
connected = false;
|
|
||||||
listeners.forEach(listener -> listener.onDisconnected());
|
|
||||||
}
|
|
||||||
redirectUrlSupplier.resetCache();
|
|
||||||
reopenEventSource();
|
|
||||||
startCheckConnectionJob();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.debug("Check: Receiving streaming events, millisSinceLastEvent={}", millisSinceLastEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the existing EventSource and opens a new EventSource as workaround when the EventSource fails to reconnect
|
|
||||||
* itself.
|
|
||||||
*/
|
|
||||||
private void reopenEventSource() {
|
|
||||||
try {
|
|
||||||
logger.debug("Reopening EventSource");
|
|
||||||
closeEventSource(10, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
logger.debug("Opening new EventSource");
|
|
||||||
SseEventSource localEventSource = createEventSource();
|
|
||||||
localEventSource.open();
|
|
||||||
|
|
||||||
eventSource = localEventSource;
|
|
||||||
} catch (FailedResolvingWWNUrlException e) {
|
|
||||||
logger.debug("Failed to resolve Nest redirect URL while opening new EventSource");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
synchronized (startStopLock) {
|
|
||||||
logger.debug("Opening EventSource and starting checkConnection job");
|
|
||||||
reopenEventSource();
|
|
||||||
startCheckConnectionJob();
|
|
||||||
logger.debug("Started");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
synchronized (startStopLock) {
|
|
||||||
logger.debug("Closing EventSource and stopping checkConnection job");
|
|
||||||
stopCheckConnectionJob(true);
|
|
||||||
closeEventSource(0, TimeUnit.SECONDS);
|
|
||||||
logger.debug("Stopped");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeEventSource(long timeout, TimeUnit timeoutUnit) {
|
|
||||||
SseEventSource localEventSource = eventSource;
|
|
||||||
if (localEventSource != null) {
|
|
||||||
if (!localEventSource.isOpen()) {
|
|
||||||
logger.debug("Existing EventSource is already closed");
|
|
||||||
} else if (localEventSource.close(timeout, timeoutUnit)) {
|
|
||||||
logger.debug("Succesfully closed existing EventSource");
|
|
||||||
} else {
|
|
||||||
logger.debug("Failed to close existing EventSource");
|
|
||||||
}
|
|
||||||
eventSource = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startCheckConnectionJob() {
|
|
||||||
ScheduledFuture<?> localCheckConnectionJob = checkConnectionJob;
|
|
||||||
if (localCheckConnectionJob == null || localCheckConnectionJob.isCancelled()) {
|
|
||||||
checkConnectionJob = scheduler.scheduleWithFixedDelay(this::checkConnection, CONNECTION_TIMEOUT_MILLIS,
|
|
||||||
KEEP_ALIVE_MILLIS, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopCheckConnectionJob(boolean mayInterruptIfRunning) {
|
|
||||||
ScheduledFuture<?> localCheckConnectionJob = checkConnectionJob;
|
|
||||||
if (localCheckConnectionJob != null && !localCheckConnectionJob.isCancelled()) {
|
|
||||||
localCheckConnectionJob.cancel(mayInterruptIfRunning);
|
|
||||||
checkConnectionJob = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean addStreamingDataListener(WWNStreamingDataListener listener) {
|
|
||||||
return listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeStreamingDataListener(WWNStreamingDataListener listener) {
|
|
||||||
return listeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable WWNTopLevelData getLastReceivedTopLevelData() {
|
|
||||||
return lastReceivedTopLevelData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onEvent(InboundSseEvent inboundEvent) {
|
|
||||||
try {
|
|
||||||
lastEventTimestamp = System.currentTimeMillis();
|
|
||||||
|
|
||||||
String name = inboundEvent.getName();
|
|
||||||
String data = inboundEvent.readData();
|
|
||||||
|
|
||||||
logger.debug("Received '{}' event, data: {}", name, data);
|
|
||||||
|
|
||||||
if (!connected) {
|
|
||||||
logger.debug("Connected to streaming events");
|
|
||||||
connected = true;
|
|
||||||
listeners.forEach(listener -> listener.onConnected());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AUTH_REVOKED.equals(name)) {
|
|
||||||
logger.debug("API authorization has been revoked for access token: {}", data);
|
|
||||||
listeners.forEach(listener -> listener.onAuthorizationRevoked(data));
|
|
||||||
} else if (ERROR.equals(name)) {
|
|
||||||
logger.warn("Error occurred: {}", data);
|
|
||||||
listeners.forEach(listener -> listener.onError(data));
|
|
||||||
} else if (KEEP_ALIVE.equals(name)) {
|
|
||||||
logger.debug("Received message to keep connection alive");
|
|
||||||
} else if (OPEN.equals(name)) {
|
|
||||||
logger.debug("Event stream opened");
|
|
||||||
} else if (PUT.equals(name)) {
|
|
||||||
logger.debug("Data has changed (or initial data sent)");
|
|
||||||
WWNTopLevelData topLevelData = WWNUtils.fromJson(data, WWNTopLevelStreamingData.class).getData();
|
|
||||||
lastReceivedTopLevelData = topLevelData;
|
|
||||||
listeners.forEach(listener -> listener.onNewTopLevelData(topLevelData));
|
|
||||||
} else {
|
|
||||||
logger.debug("Received unhandled event with name '{}' and data '{}'", name, data);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// catch exceptions here otherwise they will be swallowed by the implementation
|
|
||||||
logger.warn("An exception occurred while processing the inbound event", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onError(Throwable error) {
|
|
||||||
logger.debug("Error occurred while receiving events", error);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.update;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNull;
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNIdentifiable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.dto.WWNTopLevelData;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.listener.WWNThingDataListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles all Nest data updates through delegation to the {@link WWNUpdateHandler} for the respective data type.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNCompositeUpdateHandler {
|
|
||||||
|
|
||||||
private final Supplier<Set<String>> presentNestIdsSupplier;
|
|
||||||
private final Map<Class<?>, WWNUpdateHandler<?>> updateHandlersMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public WWNCompositeUpdateHandler(Supplier<Set<String>> presentNestIdsSupplier) {
|
|
||||||
this.presentNestIdsSupplier = presentNestIdsSupplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> boolean addListener(Class<T> dataClass, WWNThingDataListener<T> listener) {
|
|
||||||
return getOrCreateUpdateHandler(dataClass).addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> boolean addListener(Class<T> dataClass, String nestId, WWNThingDataListener<T> listener) {
|
|
||||||
return getOrCreateUpdateHandler(dataClass).addListener(nestId, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> findMissingNestIds(Set<WWNIdentifiable> updates) {
|
|
||||||
Set<String> nestIds = updates.stream().map(u -> u.getId()).collect(Collectors.toSet());
|
|
||||||
Set<String> missingNestIds = presentNestIdsSupplier.get();
|
|
||||||
missingNestIds.removeAll(nestIds);
|
|
||||||
return missingNestIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable <T> T getLastUpdate(Class<T> dataClass, String nestId) {
|
|
||||||
return getOrCreateUpdateHandler(dataClass).getLastUpdate(nestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> List<T> getLastUpdates(Class<T> dataClass) {
|
|
||||||
return getOrCreateUpdateHandler(dataClass).getLastUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<WWNIdentifiable> getNestUpdates(WWNTopLevelData data) {
|
|
||||||
Set<WWNIdentifiable> updates = new HashSet<>();
|
|
||||||
if (data.getDevices() != null) {
|
|
||||||
if (data.getDevices().getCameras() != null) {
|
|
||||||
updates.addAll(data.getDevices().getCameras().values());
|
|
||||||
}
|
|
||||||
if (data.getDevices().getSmokeCoAlarms() != null) {
|
|
||||||
updates.addAll(data.getDevices().getSmokeCoAlarms().values());
|
|
||||||
}
|
|
||||||
if (data.getDevices().getThermostats() != null) {
|
|
||||||
updates.addAll(data.getDevices().getThermostats().values());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.getStructures() != null) {
|
|
||||||
updates.addAll(data.getStructures().values());
|
|
||||||
}
|
|
||||||
return updates;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <@NonNull T> WWNUpdateHandler<T> getOrCreateUpdateHandler(Class<T> dataClass) {
|
|
||||||
WWNUpdateHandler<T> handler = (WWNUpdateHandler<T>) updateHandlersMap.get(dataClass);
|
|
||||||
if (handler == null) {
|
|
||||||
handler = new WWNUpdateHandler<>();
|
|
||||||
updateHandlersMap.put(dataClass, handler);
|
|
||||||
}
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void handleUpdate(WWNTopLevelData data) {
|
|
||||||
Set<WWNIdentifiable> updates = getNestUpdates(data);
|
|
||||||
updates.forEach(update -> {
|
|
||||||
Class<WWNIdentifiable> updateClass = (Class<WWNIdentifiable>) update.getClass();
|
|
||||||
getOrCreateUpdateHandler(updateClass).handleUpdate(updateClass, update.getId(), update);
|
|
||||||
});
|
|
||||||
|
|
||||||
Set<String> missingNestIds = findMissingNestIds(updates);
|
|
||||||
if (!missingNestIds.isEmpty()) {
|
|
||||||
updateHandlersMap.values().forEach(handler -> {
|
|
||||||
handler.handleMissingNestIds(missingNestIds);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> boolean removeListener(Class<T> dataClass, WWNThingDataListener<T> listener) {
|
|
||||||
return getOrCreateUpdateHandler(dataClass).removeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> boolean removeListener(Class<T> dataClass, String nestId, WWNThingDataListener<T> listener) {
|
|
||||||
return getOrCreateUpdateHandler(dataClass).removeListener(nestId, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resendLastUpdates() {
|
|
||||||
updateHandlersMap.values().forEach(handler -> {
|
|
||||||
handler.resendLastUpdates();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.update;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNull;
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.listener.WWNThingDataListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the updates of one type of data by notifying listeners of changes and storing the update value.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*
|
|
||||||
* @param <T> the type of update data
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNUpdateHandler<@NonNull T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID used for listeners that subscribe to any Nest update.
|
|
||||||
*/
|
|
||||||
private static final String ANY_ID = "*";
|
|
||||||
|
|
||||||
private final Map<String, T> lastUpdates = new ConcurrentHashMap<>();
|
|
||||||
private final Map<String, Set<WWNThingDataListener<T>>> listenersMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public boolean addListener(WWNThingDataListener<T> listener) {
|
|
||||||
return addListener(ANY_ID, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean addListener(String nestId, WWNThingDataListener<T> listener) {
|
|
||||||
return getOrCreateListeners(nestId).add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable T getLastUpdate(String nestId) {
|
|
||||||
return lastUpdates.get(nestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<T> getLastUpdates() {
|
|
||||||
return new ArrayList<>(lastUpdates.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<WWNThingDataListener<T>> getListeners(String nestId) {
|
|
||||||
Set<WWNThingDataListener<T>> listeners = new HashSet<>();
|
|
||||||
Set<WWNThingDataListener<T>> idListeners = listenersMap.get(nestId);
|
|
||||||
if (idListeners != null) {
|
|
||||||
listeners.addAll(idListeners);
|
|
||||||
}
|
|
||||||
Set<WWNThingDataListener<T>> anyListeners = listenersMap.get(ANY_ID);
|
|
||||||
if (anyListeners != null) {
|
|
||||||
listeners.addAll(anyListeners);
|
|
||||||
}
|
|
||||||
return listeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<WWNThingDataListener<T>> getOrCreateListeners(String nestId) {
|
|
||||||
Set<WWNThingDataListener<T>> listeners = listenersMap.get(nestId);
|
|
||||||
if (listeners == null) {
|
|
||||||
listeners = new CopyOnWriteArraySet<>();
|
|
||||||
listenersMap.put(nestId, listeners);
|
|
||||||
}
|
|
||||||
return listeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleMissingNestIds(Set<String> nestIds) {
|
|
||||||
nestIds.forEach(nestId -> {
|
|
||||||
lastUpdates.remove(nestId);
|
|
||||||
getListeners(nestId).forEach(l -> l.onMissingData(nestId));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleUpdate(Class<T> dataClass, String nestId, T update) {
|
|
||||||
final @Nullable T lastUpdate = getLastUpdate(nestId);
|
|
||||||
lastUpdates.put(nestId, update);
|
|
||||||
notifyListeners(nestId, lastUpdate, update);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyListeners(String nestId, @Nullable T lastUpdate, T update) {
|
|
||||||
Set<WWNThingDataListener<T>> listeners = getListeners(nestId);
|
|
||||||
if (lastUpdate == null) {
|
|
||||||
listeners.forEach(l -> l.onNewData(update));
|
|
||||||
} else if (!lastUpdate.equals(update)) {
|
|
||||||
listeners.forEach(l -> l.onUpdatedData(lastUpdate, update));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeListener(WWNThingDataListener<T> listener) {
|
|
||||||
return removeListener(ANY_ID, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeListener(String nestId, WWNThingDataListener<T> listener) {
|
|
||||||
return getOrCreateListeners(nestId).remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resendLastUpdates() {
|
|
||||||
lastUpdates.forEach((nestId, update) -> notifyListeners(nestId, null, update));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<config-description:config-descriptions
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
|
||||||
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:nest:wwn_account">
|
|
||||||
<parameter-group name="oauth">
|
|
||||||
<label>WWN API OAuth</label>
|
|
||||||
<description>The OAuth parameters used when communicating with the WWN API</description>
|
|
||||||
</parameter-group>
|
|
||||||
|
|
||||||
<parameter name="productId" type="text" groupName="oauth" required="true">
|
|
||||||
<label>Product ID</label>
|
|
||||||
<description>The product ID from the Nest product page</description>
|
|
||||||
</parameter>
|
|
||||||
<parameter name="productSecret" type="text" groupName="oauth" required="true">
|
|
||||||
<label>Product Secret</label>
|
|
||||||
<description>The product secret from the Nest product page</description>
|
|
||||||
</parameter>
|
|
||||||
<parameter name="accessToken" type="text" groupName="oauth" required="true">
|
|
||||||
<label>Access Token</label>
|
|
||||||
<description>The access token used for authenticating to the WWN API</description>
|
|
||||||
</parameter>
|
|
||||||
</config-description>
|
|
||||||
|
|
||||||
<config-description uri="thing-type:nest:wwn_device">
|
|
||||||
<parameter name="deviceId" type="text" required="true">
|
|
||||||
<label>Device ID</label>
|
|
||||||
</parameter>
|
|
||||||
</config-description>
|
|
||||||
|
|
||||||
<config-description uri="thing-type:nest:wwn_structure">
|
|
||||||
<parameter name="structureId" type="text" required="true">
|
|
||||||
<label>Structure ID</label>
|
|
||||||
</parameter>
|
|
||||||
</config-description>
|
|
||||||
|
|
||||||
</config-description:config-descriptions>
|
|
|
@ -15,18 +15,6 @@ thing-type.nest.sdm_doorbell.label = Nest Doorbell
|
||||||
thing-type.nest.sdm_doorbell.description = A Nest Doorbell registered with your SDM account
|
thing-type.nest.sdm_doorbell.description = A Nest Doorbell registered with your SDM account
|
||||||
thing-type.nest.sdm_thermostat.label = Nest Thermostat
|
thing-type.nest.sdm_thermostat.label = Nest Thermostat
|
||||||
thing-type.nest.sdm_thermostat.description = A Thermostat to control the various aspects of the house's HVAC system
|
thing-type.nest.sdm_thermostat.description = A Thermostat to control the various aspects of the house's HVAC system
|
||||||
thing-type.nest.wwn_account.label = Nest WWN Account
|
|
||||||
thing-type.nest.wwn_account.description = An account for using the Works with Nest (WWN) API
|
|
||||||
thing-type.nest.wwn_camera.label = Nest Cam
|
|
||||||
thing-type.nest.wwn_camera.description = A Nest Camera registered with your WWN account
|
|
||||||
thing-type.nest.wwn_camera.group.last_event.label = Last Event
|
|
||||||
thing-type.nest.wwn_camera.group.last_event.description = Information about the last camera event (requires Nest Aware subscription)
|
|
||||||
thing-type.nest.wwn_smoke_detector.label = Nest Protect
|
|
||||||
thing-type.nest.wwn_smoke_detector.description = The smoke detector/Nest Protect for the account
|
|
||||||
thing-type.nest.wwn_structure.label = Nest Structure
|
|
||||||
thing-type.nest.wwn_structure.description = The Nest structure defines the house the account has setup on Nest. You will only have more than one structure if you have more than one house
|
|
||||||
thing-type.nest.wwn_thermostat.label = Nest Thermostat
|
|
||||||
thing-type.nest.wwn_thermostat.description = A Thermostat to control the various aspects of the house's HVAC system
|
|
||||||
|
|
||||||
# thing types config
|
# thing types config
|
||||||
|
|
||||||
|
@ -55,16 +43,6 @@ thing-type.config.nest.sdm_account.sdmProjectId.description = The UUID that iden
|
||||||
thing-type.config.nest.sdm_device.deviceId.label = Device ID
|
thing-type.config.nest.sdm_device.deviceId.label = Device ID
|
||||||
thing-type.config.nest.sdm_device.refreshInterval.label = Refresh Interval
|
thing-type.config.nest.sdm_device.refreshInterval.label = Refresh Interval
|
||||||
thing-type.config.nest.sdm_device.refreshInterval.description = This is refresh interval in seconds to update the Nest device information
|
thing-type.config.nest.sdm_device.refreshInterval.description = This is refresh interval in seconds to update the Nest device information
|
||||||
thing-type.config.nest.wwn_account.accessToken.label = Access Token
|
|
||||||
thing-type.config.nest.wwn_account.accessToken.description = The access token used for authenticating to the WWN API
|
|
||||||
thing-type.config.nest.wwn_account.group.oauth.label = WWN API OAuth
|
|
||||||
thing-type.config.nest.wwn_account.group.oauth.description = The OAuth parameters used when communicating with the WWN API
|
|
||||||
thing-type.config.nest.wwn_account.productId.label = Product ID
|
|
||||||
thing-type.config.nest.wwn_account.productId.description = The product ID from the Nest product page
|
|
||||||
thing-type.config.nest.wwn_account.productSecret.label = Product Secret
|
|
||||||
thing-type.config.nest.wwn_account.productSecret.description = The product secret from the Nest product page
|
|
||||||
thing-type.config.nest.wwn_device.deviceId.label = Device ID
|
|
||||||
thing-type.config.nest.wwn_structure.structureId.label = Structure ID
|
|
||||||
|
|
||||||
# channel group types
|
# channel group types
|
||||||
|
|
||||||
|
@ -94,10 +72,6 @@ channel-group-type.nest.SDMSoundEvent.channel.image.label = Sound Event Image
|
||||||
channel-group-type.nest.SDMSoundEvent.channel.image.description = Static image based on a sound event
|
channel-group-type.nest.SDMSoundEvent.channel.image.description = Static image based on a sound event
|
||||||
channel-group-type.nest.SDMSoundEvent.channel.timestamp.label = Sound Event Timestamp
|
channel-group-type.nest.SDMSoundEvent.channel.timestamp.label = Sound Event Timestamp
|
||||||
channel-group-type.nest.SDMSoundEvent.channel.timestamp.description = The last time that a sound was detected
|
channel-group-type.nest.SDMSoundEvent.channel.timestamp.description = The last time that a sound was detected
|
||||||
channel-group-type.nest.WWNCamera.label = Camera
|
|
||||||
channel-group-type.nest.WWNCamera.description = Information about the camera
|
|
||||||
channel-group-type.nest.WWNCameraEvent.label = Camera Event
|
|
||||||
channel-group-type.nest.WWNCameraEvent.description = Information about the camera event
|
|
||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
|
|
||||||
|
@ -146,159 +120,6 @@ channel-type.nest.SDMTemperatureCool.label = Cool Temperature
|
||||||
channel-type.nest.SDMTemperatureCool.description = Lists the cool temperature setting from the thermostat
|
channel-type.nest.SDMTemperatureCool.description = Lists the cool temperature setting from the thermostat
|
||||||
channel-type.nest.SDMTemperatureHeat.label = Heat Temperature
|
channel-type.nest.SDMTemperatureHeat.label = Heat Temperature
|
||||||
channel-type.nest.SDMTemperatureHeat.description = Lists the heat temperature setting from the thermostat
|
channel-type.nest.SDMTemperatureHeat.description = Lists the heat temperature setting from the thermostat
|
||||||
channel-type.nest.WWNAppUrl.label = App URL
|
|
||||||
channel-type.nest.WWNAppUrl.description = The app URL for the camera, allows you to see the camera in an app
|
|
||||||
channel-type.nest.WWNAudioInputEnabled.label = Audio Input Enabled
|
|
||||||
channel-type.nest.WWNAudioInputEnabled.description = If the audio input is enabled for this camera
|
|
||||||
channel-type.nest.WWNAway.label = Away
|
|
||||||
channel-type.nest.WWNAway.description = Away state of the structure
|
|
||||||
channel-type.nest.WWNAway.state.option.AWAY = Away
|
|
||||||
channel-type.nest.WWNAway.state.option.HOME = Home
|
|
||||||
channel-type.nest.WWNCameraEventActivityZones.label = Activity Zones
|
|
||||||
channel-type.nest.WWNCameraEventActivityZones.description = Identifiers for activity zones that detected the event (comma separated)
|
|
||||||
channel-type.nest.WWNCameraEventAnimatedImageUrl.label = Animated Image URL
|
|
||||||
channel-type.nest.WWNCameraEventAnimatedImageUrl.description = The URL showing an animated image for the camera event
|
|
||||||
channel-type.nest.WWNCameraEventAppUrl.label = App URL
|
|
||||||
channel-type.nest.WWNCameraEventAppUrl.description = The app URL for the camera event, allows you to see the camera event in an app
|
|
||||||
channel-type.nest.WWNCameraEventEndTime.label = End Time
|
|
||||||
channel-type.nest.WWNCameraEventEndTime.description = Timestamp when the camera event ended
|
|
||||||
channel-type.nest.WWNCameraEventHasMotion.label = Has Motion
|
|
||||||
channel-type.nest.WWNCameraEventHasMotion.description = If motion was detected in the camera event
|
|
||||||
channel-type.nest.WWNCameraEventHasPerson.label = Has Person
|
|
||||||
channel-type.nest.WWNCameraEventHasPerson.description = If a person was detected in the camera event
|
|
||||||
channel-type.nest.WWNCameraEventHasSound.label = Has Sound
|
|
||||||
channel-type.nest.WWNCameraEventHasSound.description = If sound was detected in the camera event
|
|
||||||
channel-type.nest.WWNCameraEventImageUrl.label = Image URL
|
|
||||||
channel-type.nest.WWNCameraEventImageUrl.description = The URL showing an image for the camera event
|
|
||||||
channel-type.nest.WWNCameraEventStartTime.label = Start Time
|
|
||||||
channel-type.nest.WWNCameraEventStartTime.description = Timestamp when the camera event started
|
|
||||||
channel-type.nest.WWNCameraEventUrlsExpireTime.label = URLs Expire Time
|
|
||||||
channel-type.nest.WWNCameraEventUrlsExpireTime.description = Timestamp when the camera event URLs expire
|
|
||||||
channel-type.nest.WWNCameraEventWebUrl.label = Web URL
|
|
||||||
channel-type.nest.WWNCameraEventWebUrl.description = The web URL for the camera event, allows you to see the camera event in a web page
|
|
||||||
channel-type.nest.WWNCanCool.label = Can Cool
|
|
||||||
channel-type.nest.WWNCanCool.description = If the thermostat can actually turn on cooling
|
|
||||||
channel-type.nest.WWNCanHeat.label = Can Heat
|
|
||||||
channel-type.nest.WWNCanHeat.description = If the thermostat can actually turn on heating
|
|
||||||
channel-type.nest.WWNCoAlarmState.label = CO Alarm State
|
|
||||||
channel-type.nest.WWNCoAlarmState.description = Carbon monoxide alarm state
|
|
||||||
channel-type.nest.WWNCoAlarmState.state.option.OK = ok
|
|
||||||
channel-type.nest.WWNCoAlarmState.state.option.EMERGENCY = emergency
|
|
||||||
channel-type.nest.WWNCoAlarmState.state.option.WARNING = warning
|
|
||||||
channel-type.nest.WWNCountryCode.label = Country Code
|
|
||||||
channel-type.nest.WWNCountryCode.description = Country code of the structure
|
|
||||||
channel-type.nest.WWNEcoMaxSetPoint.label = Eco Max Set Point
|
|
||||||
channel-type.nest.WWNEcoMaxSetPoint.description = The eco range max set point temperature
|
|
||||||
channel-type.nest.WWNEcoMinSetPoint.label = Eco Min Set Point
|
|
||||||
channel-type.nest.WWNEcoMinSetPoint.description = The eco range min set point temperature
|
|
||||||
channel-type.nest.WWNEtaBegin.label = ETA
|
|
||||||
channel-type.nest.WWNEtaBegin.description = Estimated time of arrival at home, will setup the heat to turn on and be warm by the time you arrive
|
|
||||||
channel-type.nest.WWNFanTimerActive.label = Fan Timer Active
|
|
||||||
channel-type.nest.WWNFanTimerActive.description = If the fan timer is engaged
|
|
||||||
channel-type.nest.WWNFanTimerDuration.label = Fan Timer Duration
|
|
||||||
channel-type.nest.WWNFanTimerDuration.description = Length of time that the fan is set to run
|
|
||||||
channel-type.nest.WWNFanTimerDuration.state.option.15 = 15 min
|
|
||||||
channel-type.nest.WWNFanTimerDuration.state.option.30 = 30 min
|
|
||||||
channel-type.nest.WWNFanTimerDuration.state.option.45 = 45 min
|
|
||||||
channel-type.nest.WWNFanTimerDuration.state.option.60 = 1 h
|
|
||||||
channel-type.nest.WWNFanTimerDuration.state.option.120 = 2 h
|
|
||||||
channel-type.nest.WWNFanTimerDuration.state.option.240 = 4 h
|
|
||||||
channel-type.nest.WWNFanTimerDuration.state.option.480 = 8 h
|
|
||||||
channel-type.nest.WWNFanTimerDuration.state.option.960 = 16 h
|
|
||||||
channel-type.nest.WWNFanTimerTimeout.label = Fan Timer Timeout
|
|
||||||
channel-type.nest.WWNFanTimerTimeout.description = Timestamp when the fan stops running
|
|
||||||
channel-type.nest.WWNHasFan.label = Has Fan
|
|
||||||
channel-type.nest.WWNHasFan.description = If the thermostat can control the fan
|
|
||||||
channel-type.nest.WWNHasLeaf.label = Has Leaf
|
|
||||||
channel-type.nest.WWNHasLeaf.description = If the thermostat is currently in a leaf mode
|
|
||||||
channel-type.nest.WWNHumidity.label = Humidity
|
|
||||||
channel-type.nest.WWNHumidity.description = Indicates the current relative humidity
|
|
||||||
channel-type.nest.WWNLastConnection.label = Last Connection
|
|
||||||
channel-type.nest.WWNLastConnection.description = Timestamp of the last successful interaction with Nest
|
|
||||||
channel-type.nest.WWNLastManualTestTime.label = Last Manual Test Time
|
|
||||||
channel-type.nest.WWNLastManualTestTime.description = Timestamp of the last successful manual test
|
|
||||||
channel-type.nest.WWNLastOnlineChange.label = Last Online Change
|
|
||||||
channel-type.nest.WWNLastOnlineChange.description = Timestamp of the last online status change
|
|
||||||
channel-type.nest.WWNLocked.label = Locked
|
|
||||||
channel-type.nest.WWNLocked.description = If the thermostat has the temperature locked to only be within a set range
|
|
||||||
channel-type.nest.WWNLockedMaxSetPoint.label = Locked Max Set Point
|
|
||||||
channel-type.nest.WWNLockedMaxSetPoint.description = The locked range max set point temperature
|
|
||||||
channel-type.nest.WWNLockedMinSetPoint.label = Locked Min Set Point
|
|
||||||
channel-type.nest.WWNLockedMinSetPoint.description = The locked range min set point temperature
|
|
||||||
channel-type.nest.WWNManualTestActive.label = Manual Test Active
|
|
||||||
channel-type.nest.WWNManualTestActive.description = If the manual test is currently active
|
|
||||||
channel-type.nest.WWNMaxSetPoint.label = Max Set Point
|
|
||||||
channel-type.nest.WWNMaxSetPoint.description = The max set point temperature
|
|
||||||
channel-type.nest.WWNMinSetPoint.label = Min Set Point
|
|
||||||
channel-type.nest.WWNMinSetPoint.description = The min set point temperature
|
|
||||||
channel-type.nest.WWNMode.label = Mode
|
|
||||||
channel-type.nest.WWNMode.description = Current mode of the Nest thermostat
|
|
||||||
channel-type.nest.WWNMode.state.option.OFF = off
|
|
||||||
channel-type.nest.WWNMode.state.option.ECO = eco
|
|
||||||
channel-type.nest.WWNMode.state.option.HEAT = heating
|
|
||||||
channel-type.nest.WWNMode.state.option.COOL = cooling
|
|
||||||
channel-type.nest.WWNMode.state.option.HEAT_COOL = heat/cool
|
|
||||||
channel-type.nest.WWNPeakPeriodEndTime.label = Peak Period End Time
|
|
||||||
channel-type.nest.WWNPeakPeriodEndTime.description = Peak period end for the Rush Hour Rewards program
|
|
||||||
channel-type.nest.WWNPeakPeriodStartTime.label = Peak Period Start Time
|
|
||||||
channel-type.nest.WWNPeakPeriodStartTime.description = Peak period start for the Rush Hour Rewards program
|
|
||||||
channel-type.nest.WWNPostalCode.label = Postal Code
|
|
||||||
channel-type.nest.WWNPostalCode.description = Postal code of the structure
|
|
||||||
channel-type.nest.WWNPreviousMode.label = Previous Mode
|
|
||||||
channel-type.nest.WWNPreviousMode.description = The previous mode of the Nest thermostat
|
|
||||||
channel-type.nest.WWNPreviousMode.state.option.OFF = off
|
|
||||||
channel-type.nest.WWNPreviousMode.state.option.ECO = eco
|
|
||||||
channel-type.nest.WWNPreviousMode.state.option.HEAT = heating
|
|
||||||
channel-type.nest.WWNPreviousMode.state.option.COOL = cooling
|
|
||||||
channel-type.nest.WWNPreviousMode.state.option.HEAT_COOL = heat/cool
|
|
||||||
channel-type.nest.WWNPublicShareEnabled.label = Public Share Enabled
|
|
||||||
channel-type.nest.WWNPublicShareEnabled.description = If the public sharing of this camera is enabled
|
|
||||||
channel-type.nest.WWNPublicShareUrl.label = Public Share URL
|
|
||||||
channel-type.nest.WWNPublicShareUrl.description = The publicly available URL for the camera
|
|
||||||
channel-type.nest.WWNRushHourRewardsEnrollment.label = Rush Hour Rewards
|
|
||||||
channel-type.nest.WWNRushHourRewardsEnrollment.description = If rush hour rewards system is enabled or not
|
|
||||||
channel-type.nest.WWNSecurityState.label = Security State
|
|
||||||
channel-type.nest.WWNSecurityState.description = Security state of the structure
|
|
||||||
channel-type.nest.WWNSecurityState.state.option.OK = ok
|
|
||||||
channel-type.nest.WWNSecurityState.state.option.DETER = deter
|
|
||||||
channel-type.nest.WWNSetPoint.label = Set Point
|
|
||||||
channel-type.nest.WWNSetPoint.description = The set point temperature
|
|
||||||
channel-type.nest.WWNSmokeAlarmState.label = Smoke Alarm State
|
|
||||||
channel-type.nest.WWNSmokeAlarmState.description = Smoke alarm state
|
|
||||||
channel-type.nest.WWNSmokeAlarmState.state.option.OK = ok
|
|
||||||
channel-type.nest.WWNSmokeAlarmState.state.option.EMERGENCY = emergency
|
|
||||||
channel-type.nest.WWNSmokeAlarmState.state.option.WARNING = warning
|
|
||||||
channel-type.nest.WWNSnapshotUrl.label = Snapshot URL
|
|
||||||
channel-type.nest.WWNSnapshotUrl.description = The URL showing a snapshot of the camera
|
|
||||||
channel-type.nest.WWNState.label = State
|
|
||||||
channel-type.nest.WWNState.description = The active state of the Nest thermostat
|
|
||||||
channel-type.nest.WWNState.state.option.OFF = off
|
|
||||||
channel-type.nest.WWNState.state.option.HEATING = heating
|
|
||||||
channel-type.nest.WWNState.state.option.COOLING = cooling
|
|
||||||
channel-type.nest.WWNStreaming.label = Streaming
|
|
||||||
channel-type.nest.WWNStreaming.description = If the camera is currently streaming
|
|
||||||
channel-type.nest.WWNSunlightCorrectionActive.label = Sunlight Correction Active
|
|
||||||
channel-type.nest.WWNSunlightCorrectionActive.description = If sunlight correction is active
|
|
||||||
channel-type.nest.WWNSunlightCorrectionEnabled.label = Sunlight Correction Enabled
|
|
||||||
channel-type.nest.WWNSunlightCorrectionEnabled.description = If sunlight correction is enabled
|
|
||||||
channel-type.nest.WWNTemperature.label = Temperature
|
|
||||||
channel-type.nest.WWNTemperature.description = Current temperature
|
|
||||||
channel-type.nest.WWNTimeToTarget.label = Time to Target
|
|
||||||
channel-type.nest.WWNTimeToTarget.description = Time left to the target temperature approximately
|
|
||||||
channel-type.nest.WWNTimeZone.label = Time Zone
|
|
||||||
channel-type.nest.WWNTimeZone.description = The time zone for the structure
|
|
||||||
channel-type.nest.WWNUiColorState.label = UI Color State
|
|
||||||
channel-type.nest.WWNUiColorState.description = Current color state of the protect
|
|
||||||
channel-type.nest.WWNUiColorState.state.option.GRAY = gray
|
|
||||||
channel-type.nest.WWNUiColorState.state.option.GREEN = green
|
|
||||||
channel-type.nest.WWNUiColorState.state.option.YELLOW = yellow
|
|
||||||
channel-type.nest.WWNUiColorState.state.option.RED = red
|
|
||||||
channel-type.nest.WWNUsingEmergencyHeat.label = Using Emergency Heat
|
|
||||||
channel-type.nest.WWNUsingEmergencyHeat.description = If the system is currently using emergency heat
|
|
||||||
channel-type.nest.WWNVideoHistoryEnabled.label = Video History Enabled
|
|
||||||
channel-type.nest.WWNVideoHistoryEnabled.description = If the video history is enabled for this camera
|
|
||||||
channel-type.nest.WWNWebUrl.label = Web URL
|
|
||||||
channel-type.nest.WWNWebUrl.description = The web URL for the camera, allows you to see the camera in a web page
|
|
||||||
|
|
||||||
# channel types config
|
# channel types config
|
||||||
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<thing:thing-descriptions bindingId="nest"
|
|
||||||
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">
|
|
||||||
|
|
||||||
<bridge-type id="wwn_account">
|
|
||||||
<label>Nest WWN Account</label>
|
|
||||||
<description>An account for using the Works with Nest (WWN) API</description>
|
|
||||||
<config-description-ref uri="thing-type:nest:wwn_account"/>
|
|
||||||
</bridge-type>
|
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
|
|
@ -1,32 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<thing:thing-descriptions bindingId="nest"
|
|
||||||
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="wwn_camera" listed="false">
|
|
||||||
<supported-bridge-type-refs>
|
|
||||||
<bridge-type-ref id="wwn_account"/>
|
|
||||||
</supported-bridge-type-refs>
|
|
||||||
|
|
||||||
<label>Nest Cam</label>
|
|
||||||
<description>A Nest Camera registered with your WWN account</description>
|
|
||||||
|
|
||||||
<channel-groups>
|
|
||||||
<channel-group id="camera" typeId="WWNCamera"/>
|
|
||||||
<channel-group id="last_event" typeId="WWNCameraEvent">
|
|
||||||
<label>Last Event</label>
|
|
||||||
<description>Information about the last camera event (requires Nest Aware subscription)</description>
|
|
||||||
</channel-group>
|
|
||||||
</channel-groups>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<property name="vendor">Nest</property>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<representation-property>deviceId</representation-property>
|
|
||||||
|
|
||||||
<config-description-ref uri="thing-type:nest:wwn_device"/>
|
|
||||||
</thing-type>
|
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
|
|
@ -1,521 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<thing:thing-descriptions bindingId="nest"
|
|
||||||
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">
|
|
||||||
|
|
||||||
<!-- Common -->
|
|
||||||
<channel-type id="WWNLastConnection" advanced="true">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>Last Connection</label>
|
|
||||||
<description>Timestamp of the last successful interaction with Nest</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<!-- Structure -->
|
|
||||||
<channel-type id="WWNAway">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Away</label>
|
|
||||||
<description>Away state of the structure</description>
|
|
||||||
<state>
|
|
||||||
<options>
|
|
||||||
<option value="AWAY">Away</option>
|
|
||||||
<option value="HOME">Home</option>
|
|
||||||
</options>
|
|
||||||
</state>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCountryCode" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Country Code</label>
|
|
||||||
<description>Country code of the structure</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNPostalCode" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Postal Code</label>
|
|
||||||
<description>Postal code of the structure</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNTimeZone">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Time Zone</label>
|
|
||||||
<description>The time zone for the structure</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNPeakPeriodStartTime" advanced="true">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>Peak Period Start Time</label>
|
|
||||||
<description>Peak period start for the Rush Hour Rewards program</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNPeakPeriodEndTime" advanced="true">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>Peak Period End Time</label>
|
|
||||||
<description>Peak period end for the Rush Hour Rewards program</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNEtaBegin" advanced="true">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>ETA</label>
|
|
||||||
<description>
|
|
||||||
Estimated time of arrival at home, will setup the heat to turn on and be warm
|
|
||||||
by the time you arrive
|
|
||||||
</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNRushHourRewardsEnrollment">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Rush Hour Rewards</label>
|
|
||||||
<description>If rush hour rewards system is enabled or not</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNSecurityState">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Security State</label>
|
|
||||||
<description>Security state of the structure</description>
|
|
||||||
<state readOnly="true">
|
|
||||||
<options>
|
|
||||||
<option value="OK">ok</option>
|
|
||||||
<option value="DETER">deter</option>
|
|
||||||
</options>
|
|
||||||
</state>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<!-- Camera -->
|
|
||||||
<channel-group-type id="WWNCamera">
|
|
||||||
<label>Camera</label>
|
|
||||||
<description>Information about the camera</description>
|
|
||||||
<channels>
|
|
||||||
<channel id="streaming" typeId="WWNStreaming"/>
|
|
||||||
<channel id="audio_input_enabled" typeId="WWNAudioInputEnabled"/>
|
|
||||||
<channel id="public_share_enabled" typeId="WWNPublicShareEnabled"/>
|
|
||||||
<channel id="video_history_enabled" typeId="WWNVideoHistoryEnabled"/>
|
|
||||||
<channel id="app_url" typeId="WWNAppUrl"/>
|
|
||||||
<channel id="snapshot_url" typeId="WWNSnapshotUrl"/>
|
|
||||||
<channel id="public_share_url" typeId="WWNPublicShareUrl"/>
|
|
||||||
<channel id="web_url" typeId="WWNWebUrl"/>
|
|
||||||
<channel id="last_online_change" typeId="WWNLastOnlineChange"/>
|
|
||||||
</channels>
|
|
||||||
</channel-group-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNAudioInputEnabled" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Audio Input Enabled</label>
|
|
||||||
<description>If the audio input is enabled for this camera</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNVideoHistoryEnabled" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Video History Enabled</label>
|
|
||||||
<description>If the video history is enabled for this camera</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNPublicShareEnabled" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Public Share Enabled</label>
|
|
||||||
<description>If the public sharing of this camera is enabled</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNStreaming">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Streaming</label>
|
|
||||||
<description>If the camera is currently streaming</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNWebUrl">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Web URL</label>
|
|
||||||
<description>The web URL for the camera, allows you to see the camera in a web page</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNPublicShareUrl">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Public Share URL</label>
|
|
||||||
<description>The publicly available URL for the camera</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNSnapshotUrl" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Snapshot URL</label>
|
|
||||||
<description>The URL showing a snapshot of the camera</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNAppUrl" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>App URL</label>
|
|
||||||
<description>The app URL for the camera, allows you to see the camera in an app</description>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNLastOnlineChange" advanced="true">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>Last Online Change</label>
|
|
||||||
<description>Timestamp of the last online status change</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-group-type id="WWNCameraEvent">
|
|
||||||
<label>Camera Event</label>
|
|
||||||
<description>Information about the camera event</description>
|
|
||||||
<channels>
|
|
||||||
<channel id="has_motion" typeId="WWNCameraEventHasMotion"/>
|
|
||||||
<channel id="has_sound" typeId="WWNCameraEventHasSound"/>
|
|
||||||
<channel id="has_person" typeId="WWNCameraEventHasPerson"/>
|
|
||||||
<channel id="start_time" typeId="WWNCameraEventStartTime"/>
|
|
||||||
<channel id="end_time" typeId="WWNCameraEventEndTime"/>
|
|
||||||
<channel id="urls_expire_time" typeId="WWNCameraEventUrlsExpireTime"/>
|
|
||||||
<channel id="animated_image_url" typeId="WWNCameraEventAnimatedImageUrl"/>
|
|
||||||
<channel id="app_url" typeId="WWNCameraEventAppUrl"/>
|
|
||||||
<channel id="image_url" typeId="WWNCameraEventImageUrl"/>
|
|
||||||
<channel id="web_url" typeId="WWNCameraEventWebUrl"/>
|
|
||||||
<channel id="activity_zones" typeId="WWNCameraEventActivityZones"/>
|
|
||||||
</channels>
|
|
||||||
</channel-group-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventHasSound" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Has Sound</label>
|
|
||||||
<description>If sound was detected in the camera event</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventHasMotion" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Has Motion</label>
|
|
||||||
<description>If motion was detected in the camera event</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventHasPerson" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Has Person</label>
|
|
||||||
<description>If a person was detected in the camera event</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventStartTime" advanced="true">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>Start Time</label>
|
|
||||||
<description>Timestamp when the camera event started</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventEndTime" advanced="true">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>End Time</label>
|
|
||||||
<description>Timestamp when the camera event ended</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventUrlsExpireTime" advanced="true">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>URLs Expire Time</label>
|
|
||||||
<description>Timestamp when the camera event URLs expire</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventWebUrl" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Web URL</label>
|
|
||||||
<description>The web URL for the camera event, allows you to see the camera event in a web page</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventAppUrl" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>App URL</label>
|
|
||||||
<description>The app URL for the camera event, allows you to see the camera event in an app</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventImageUrl" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Image URL</label>
|
|
||||||
<description>The URL showing an image for the camera event</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventAnimatedImageUrl" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Animated Image URL</label>
|
|
||||||
<description>The URL showing an animated image for the camera event</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCameraEventActivityZones" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Activity Zones</label>
|
|
||||||
<description>Identifiers for activity zones that detected the event (comma separated)</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<!-- Smoke detector -->
|
|
||||||
<channel-type id="WWNUiColorState" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>UI Color State</label>
|
|
||||||
<description>Current color state of the protect</description>
|
|
||||||
<state readOnly="true">
|
|
||||||
<options>
|
|
||||||
<option value="GRAY">gray</option>
|
|
||||||
<option value="GREEN">green</option>
|
|
||||||
<option value="YELLOW">yellow</option>
|
|
||||||
<option value="RED">red</option>
|
|
||||||
</options>
|
|
||||||
</state>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCoAlarmState">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>CO Alarm State</label>
|
|
||||||
<description>Carbon monoxide alarm state</description>
|
|
||||||
<state readOnly="true">
|
|
||||||
<options>
|
|
||||||
<option value="OK">ok</option>
|
|
||||||
<option value="EMERGENCY">emergency</option>
|
|
||||||
<option value="WARNING">warning</option>
|
|
||||||
</options>
|
|
||||||
</state>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNSmokeAlarmState">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Smoke Alarm State</label>
|
|
||||||
<description>Smoke alarm state</description>
|
|
||||||
<state readOnly="true">
|
|
||||||
<options>
|
|
||||||
<option value="OK">ok</option>
|
|
||||||
<option value="EMERGENCY">emergency</option>
|
|
||||||
<option value="WARNING">warning</option>
|
|
||||||
</options>
|
|
||||||
</state>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNManualTestActive" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Manual Test Active</label>
|
|
||||||
<description>If the manual test is currently active</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNLastManualTestTime" advanced="true">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>Last Manual Test Time</label>
|
|
||||||
<description>Timestamp of the last successful manual test</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<!-- Thermostat -->
|
|
||||||
<channel-type id="WWNTemperature">
|
|
||||||
<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="WWNSetPoint">
|
|
||||||
<item-type>Number:Temperature</item-type>
|
|
||||||
<label>Set Point</label>
|
|
||||||
<description>The set point temperature</description>
|
|
||||||
<category>Temperature</category>
|
|
||||||
<state pattern="%.1f %unit%" step="0.5"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNMaxSetPoint">
|
|
||||||
<item-type>Number:Temperature</item-type>
|
|
||||||
<label>Max Set Point</label>
|
|
||||||
<description>The max set point temperature</description>
|
|
||||||
<category>Temperature</category>
|
|
||||||
<state pattern="%.1f %unit%" step="0.5"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNMinSetPoint">
|
|
||||||
<item-type>Number:Temperature</item-type>
|
|
||||||
<label>Min Set Point</label>
|
|
||||||
<description>The min set point temperature</description>
|
|
||||||
<category>Temperature</category>
|
|
||||||
<state pattern="%.1f %unit%" step="0.5"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNEcoMaxSetPoint" advanced="true">
|
|
||||||
<item-type>Number:Temperature</item-type>
|
|
||||||
<label>Eco Max Set Point</label>
|
|
||||||
<description>The eco range max set point temperature</description>
|
|
||||||
<category>Temperature</category>
|
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNEcoMinSetPoint" advanced="true">
|
|
||||||
<item-type>Number:Temperature</item-type>
|
|
||||||
<label>Eco Min Set Point</label>
|
|
||||||
<description>The eco range min set point temperature</description>
|
|
||||||
<category>Temperature</category>
|
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNLockedMaxSetPoint" advanced="true">
|
|
||||||
<item-type>Number:Temperature</item-type>
|
|
||||||
<label>Locked Max Set Point</label>
|
|
||||||
<description>The locked range max set point temperature</description>
|
|
||||||
<category>Temperature</category>
|
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNLockedMinSetPoint" advanced="true">
|
|
||||||
<item-type>Number:Temperature</item-type>
|
|
||||||
<label>Locked Min Set Point</label>
|
|
||||||
<description>The locked range min set point temperature</description>
|
|
||||||
<category>Temperature</category>
|
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNLocked" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Locked</label>
|
|
||||||
<description>If the thermostat has the temperature locked to only be within a set range</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNMode">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Mode</label>
|
|
||||||
<description>Current mode of the Nest thermostat</description>
|
|
||||||
<state>
|
|
||||||
<options>
|
|
||||||
<option value="OFF">off</option>
|
|
||||||
<option value="ECO">eco</option>
|
|
||||||
<option value="HEAT">heating</option>
|
|
||||||
<option value="COOL">cooling</option>
|
|
||||||
<option value="HEAT_COOL">heat/cool</option>
|
|
||||||
</options>
|
|
||||||
</state>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNPreviousMode" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>Previous Mode</label>
|
|
||||||
<description>The previous mode of the Nest thermostat</description>
|
|
||||||
<state readOnly="true">
|
|
||||||
<options>
|
|
||||||
<option value="OFF">off</option>
|
|
||||||
<option value="ECO">eco</option>
|
|
||||||
<option value="HEAT">heating</option>
|
|
||||||
<option value="COOL">cooling</option>
|
|
||||||
<option value="HEAT_COOL">heat/cool</option>
|
|
||||||
</options>
|
|
||||||
</state>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNState" advanced="true">
|
|
||||||
<item-type>String</item-type>
|
|
||||||
<label>State</label>
|
|
||||||
<description>The active state of the Nest thermostat</description>
|
|
||||||
<state readOnly="true">
|
|
||||||
<options>
|
|
||||||
<option value="OFF">off</option>
|
|
||||||
<option value="HEATING">heating</option>
|
|
||||||
<option value="COOLING">cooling</option>
|
|
||||||
</options>
|
|
||||||
</state>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNHumidity">
|
|
||||||
<item-type>Number:Dimensionless</item-type>
|
|
||||||
<label>Humidity</label>
|
|
||||||
<description>Indicates the current relative humidity</description>
|
|
||||||
<category>Humidity</category>
|
|
||||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNTimeToTarget">
|
|
||||||
<item-type>Number:Time</item-type>
|
|
||||||
<label>Time to Target</label>
|
|
||||||
<description>Time left to the target temperature approximately</description>
|
|
||||||
<state pattern="%d %unit%" readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCanHeat" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Can Heat</label>
|
|
||||||
<description>If the thermostat can actually turn on heating</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNCanCool" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Can Cool</label>
|
|
||||||
<description>If the thermostat can actually turn on cooling</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNFanTimerActive" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Fan Timer Active</label>
|
|
||||||
<description>If the fan timer is engaged</description>
|
|
||||||
<state/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNFanTimerDuration" advanced="true">
|
|
||||||
<item-type>Number:Time</item-type>
|
|
||||||
<label>Fan Timer Duration</label>
|
|
||||||
<description>Length of time that the fan is set to run</description>
|
|
||||||
<state>
|
|
||||||
<options>
|
|
||||||
<option value="15">15 min</option>
|
|
||||||
<option value="30">30 min</option>
|
|
||||||
<option value="45">45 min</option>
|
|
||||||
<option value="60">1 h</option>
|
|
||||||
<option value="120">2 h</option>
|
|
||||||
<option value="240">4 h</option>
|
|
||||||
<option value="480">8 h</option>
|
|
||||||
<option value="960">16 h</option>
|
|
||||||
</options>
|
|
||||||
</state>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNFanTimerTimeout" advanced="true">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>Fan Timer Timeout</label>
|
|
||||||
<description>Timestamp when the fan stops running</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNHasFan" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Has Fan</label>
|
|
||||||
<description>If the thermostat can control the fan</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNHasLeaf" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Has Leaf</label>
|
|
||||||
<description>If the thermostat is currently in a leaf mode</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNSunlightCorrectionEnabled" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Sunlight Correction Enabled</label>
|
|
||||||
<description>If sunlight correction is enabled</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNSunlightCorrectionActive" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Sunlight Correction Active</label>
|
|
||||||
<description>If sunlight correction is active</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="WWNUsingEmergencyHeat" advanced="true">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Using Emergency Heat</label>
|
|
||||||
<description>If the system is currently using emergency heat</description>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
|
|
@ -1,34 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<thing:thing-descriptions bindingId="nest"
|
|
||||||
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="wwn_smoke_detector" listed="false">
|
|
||||||
<supported-bridge-type-refs>
|
|
||||||
<bridge-type-ref id="wwn_account"/>
|
|
||||||
</supported-bridge-type-refs>
|
|
||||||
|
|
||||||
<label>Nest Protect</label>
|
|
||||||
<description>The smoke detector/Nest Protect for the account</description>
|
|
||||||
|
|
||||||
<channels>
|
|
||||||
<channel id="ui_color_state" typeId="WWNUiColorState"/>
|
|
||||||
<channel id="low_battery" typeId="system.low-battery"/>
|
|
||||||
<channel id="co_alarm_state" typeId="WWNCoAlarmState"/>
|
|
||||||
<channel id="smoke_alarm_state" typeId="WWNSmokeAlarmState"/>
|
|
||||||
<channel id="manual_test_active" typeId="WWNManualTestActive"/>
|
|
||||||
<channel id="last_manual_test_time" typeId="WWNLastManualTestTime"/>
|
|
||||||
<channel id="last_connection" typeId="WWNLastConnection"/>
|
|
||||||
</channels>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<property name="vendor">Nest</property>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<representation-property>deviceId</representation-property>
|
|
||||||
|
|
||||||
<config-description-ref uri="thing-type:nest:wwn_device"/>
|
|
||||||
</thing-type>
|
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
|
|
@ -1,40 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<thing:thing-descriptions bindingId="nest"
|
|
||||||
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="wwn_structure" listed="false">
|
|
||||||
<supported-bridge-type-refs>
|
|
||||||
<bridge-type-ref id="wwn_account"/>
|
|
||||||
</supported-bridge-type-refs>
|
|
||||||
|
|
||||||
<label>Nest Structure</label>
|
|
||||||
<description>The Nest structure defines the house the account has setup on Nest.
|
|
||||||
You will only have more than one
|
|
||||||
structure if you have more than one house</description>
|
|
||||||
|
|
||||||
<channels>
|
|
||||||
<channel id="country_code" typeId="WWNCountryCode"/>
|
|
||||||
<channel id="postal_code" typeId="WWNPostalCode"/>
|
|
||||||
<channel id="time_zone" typeId="WWNTimeZone"/>
|
|
||||||
<channel id="peak_period_start_time" typeId="WWNPeakPeriodStartTime"/>
|
|
||||||
<channel id="peak_period_end_time" typeId="WWNPeakPeriodEndTime"/>
|
|
||||||
<channel id="rush_hour_rewards_enrollment" typeId="WWNRushHourRewardsEnrollment"/>
|
|
||||||
<channel id="eta_begin" typeId="WWNEtaBegin"/>
|
|
||||||
<channel id="co_alarm_state" typeId="WWNCoAlarmState"/>
|
|
||||||
<channel id="smoke_alarm_state" typeId="WWNSmokeAlarmState"/>
|
|
||||||
<channel id="security_state" typeId="WWNSecurityState"/>
|
|
||||||
<channel id="away" typeId="WWNAway"/>
|
|
||||||
</channels>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<property name="vendor">Nest</property>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<representation-property>structureId</representation-property>
|
|
||||||
|
|
||||||
<config-description-ref uri="thing-type:nest:wwn_structure"/>
|
|
||||||
</thing-type>
|
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
|
|
@ -1,52 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<thing:thing-descriptions bindingId="nest"
|
|
||||||
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="wwn_thermostat" listed="false">
|
|
||||||
<supported-bridge-type-refs>
|
|
||||||
<bridge-type-ref id="wwn_account"/>
|
|
||||||
</supported-bridge-type-refs>
|
|
||||||
|
|
||||||
<label>Nest Thermostat</label>
|
|
||||||
<description>A Thermostat to control the various aspects of the house's HVAC system</description>
|
|
||||||
|
|
||||||
<channels>
|
|
||||||
<channel id="temperature" typeId="WWNTemperature"/>
|
|
||||||
<channel id="humidity" typeId="WWNHumidity"/>
|
|
||||||
<channel id="mode" typeId="WWNMode"/>
|
|
||||||
<channel id="previous_mode" typeId="WWNPreviousMode"/>
|
|
||||||
<channel id="state" typeId="WWNState"/>
|
|
||||||
<channel id="set_point" typeId="WWNSetPoint"/>
|
|
||||||
<channel id="max_set_point" typeId="WWNMaxSetPoint"/>
|
|
||||||
<channel id="min_set_point" typeId="WWNMinSetPoint"/>
|
|
||||||
<channel id="can_heat" typeId="WWNCanHeat"/>
|
|
||||||
<channel id="can_cool" typeId="WWNCanCool"/>
|
|
||||||
<channel id="fan_timer_active" typeId="WWNFanTimerActive"/>
|
|
||||||
<channel id="fan_timer_duration" typeId="WWNFanTimerDuration"/>
|
|
||||||
<channel id="fan_timer_timeout" typeId="WWNFanTimerTimeout"/>
|
|
||||||
<channel id="has_fan" typeId="WWNHasFan"/>
|
|
||||||
<channel id="has_leaf" typeId="WWNHasLeaf"/>
|
|
||||||
<channel id="sunlight_correction_enabled" typeId="WWNSunlightCorrectionEnabled"/>
|
|
||||||
<channel id="sunlight_correction_active" typeId="WWNSunlightCorrectionActive"/>
|
|
||||||
<channel id="using_emergency_heat" typeId="WWNUsingEmergencyHeat"/>
|
|
||||||
<channel id="eco_max_set_point" typeId="WWNEcoMaxSetPoint"/>
|
|
||||||
<channel id="eco_min_set_point" typeId="WWNEcoMinSetPoint"/>
|
|
||||||
<channel id="locked" typeId="WWNLocked"/>
|
|
||||||
<channel id="locked_max_set_point" typeId="WWNLockedMaxSetPoint"/>
|
|
||||||
<channel id="locked_min_set_point" typeId="WWNLockedMinSetPoint"/>
|
|
||||||
<channel id="time_to_target" typeId="WWNTimeToTarget"/>
|
|
||||||
<channel id="last_connection" typeId="WWNLastConnection"/>
|
|
||||||
</channels>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<property name="vendor">Nest</property>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<representation-property>deviceId</representation-property>
|
|
||||||
|
|
||||||
<config-description-ref uri="thing-type:nest:wwn_device"/>
|
|
||||||
</thing-type>
|
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
|
|
@ -1,13 +0,0 @@
|
||||||
This content is produced and maintained by the openHAB project.
|
|
||||||
|
|
||||||
* Project home: https://www.openhab.org
|
|
||||||
|
|
||||||
== Declared Project Licenses
|
|
||||||
|
|
||||||
This program and the accompanying materials are made available under the terms
|
|
||||||
of the Eclipse Public License 2.0 which is available at
|
|
||||||
https://www.eclipse.org/legal/epl-2.0/.
|
|
||||||
|
|
||||||
== Source Code
|
|
||||||
|
|
||||||
https://github.com/openhab/openhab-addons
|
|
|
@ -1,7 +0,0 @@
|
||||||
# Nest Binding Tests
|
|
||||||
|
|
||||||
[Nest Labs](https://nest.com/) developed/acquired the Wi-Fi enabled Nest Learning Thermostat, the Nest Protect Smoke+CO detector, and the Nest Cam.
|
|
||||||
These devices are supported by this binding, which communicates with the Nest API over a secure, RESTful API to Nest's servers.
|
|
||||||
Monitoring ambient temperature and humidity, changing HVAC mode, changing heat or cool setpoints, monitoring and changing your "home/away" status, and monitoring your Nest Protects and Nest Cams can be accomplished through this binding.
|
|
||||||
|
|
||||||
This binding is to test and verify the nest binding.
|
|
|
@ -1,112 +0,0 @@
|
||||||
-include: ../itest-common.bndrun
|
|
||||||
|
|
||||||
Bundle-SymbolicName: ${project.artifactId}
|
|
||||||
Fragment-Host: org.openhab.binding.nest
|
|
||||||
|
|
||||||
-runrequires: \
|
|
||||||
bnd.identity;id='org.openhab.binding.nest.tests'
|
|
||||||
|
|
||||||
# We would like to use the "volatile" storage only
|
|
||||||
-runblacklist: \
|
|
||||||
bnd.identity;id='org.openhab.core.storage.json',\
|
|
||||||
bnd.identity;id='jakarta.ws.rs-api'
|
|
||||||
|
|
||||||
#
|
|
||||||
# done
|
|
||||||
#
|
|
||||||
-runbundles: \
|
|
||||||
org.eclipse.equinox.event;version='[1.4.300,1.4.301)',\
|
|
||||||
org.osgi.service.event;version='[1.4.0,1.4.1)',\
|
|
||||||
org.osgi.service.jaxrs;version='[1.0.0,1.0.1)',\
|
|
||||||
org.hamcrest;version='[2.2.0,2.2.1)',\
|
|
||||||
org.opentest4j;version='[1.2.0,1.2.1)',\
|
|
||||||
jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\
|
|
||||||
com.sun.xml.bind.jaxb-osgi;version='[2.3.3,2.3.4)',\
|
|
||||||
org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\
|
|
||||||
org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\
|
|
||||||
org.apache.aries.javax.jax.rs-api;version='[1.0.1,1.0.2)',\
|
|
||||||
jakarta.annotation-api;version='[1.3.5,1.3.6)',\
|
|
||||||
org.apache.aries.component-dsl.component-dsl;version='[1.2.2,1.2.3)',\
|
|
||||||
stax2-api;version='[4.2.1,4.2.2)',\
|
|
||||||
jakarta.inject.jakarta.inject-api;version='[2.0.0,2.0.1)',\
|
|
||||||
org.glassfish.hk2.external.javax.inject;version='[2.4.0,2.4.1)',\
|
|
||||||
si-units;version='[2.1.0,2.1.1)',\
|
|
||||||
si.uom.si-quantity;version='[2.1.0,2.1.1)',\
|
|
||||||
org.apache.aries.jax.rs.whiteboard;version='[2.0.0,2.0.1)',\
|
|
||||||
org.osgi.util.function;version='[1.2.0,1.2.1)',\
|
|
||||||
org.osgi.util.promise;version='[1.2.0,1.2.1)',\
|
|
||||||
ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
|
|
||||||
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
|
|
||||||
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
|
|
||||||
org.jsr-305;version='[3.0.2,3.0.3)',\
|
|
||||||
com.google.gson;version='[2.9.1,2.9.2)',\
|
|
||||||
org.objectweb.asm;version='[9.4.0,9.4.1)',\
|
|
||||||
com.sun.jna;version='[5.12.1,5.12.2)',\
|
|
||||||
org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\
|
|
||||||
org.apache.felix.http.servlet-api;version='[1.2.0,1.2.1)',\
|
|
||||||
org.apache.felix.scr;version='[2.2.4,2.2.5)',\
|
|
||||||
org.eclipse.jetty.client;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.http;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.io;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.jaas;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.security;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.server;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.servlet;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.util;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.util.ajax;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.websocket.api;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.websocket.client;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.websocket.common;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.xml;version='[9.4.50,9.4.51)',\
|
|
||||||
org.ops4j.pax.logging.pax-logging-api;version='[2.2.0,2.2.1)',\
|
|
||||||
org.ops4j.pax.web.pax-web-api;version='[8.0.15,8.0.16)',\
|
|
||||||
org.ops4j.pax.web.pax-web-jetty;version='[8.0.15,8.0.16)',\
|
|
||||||
org.ops4j.pax.web.pax-web-runtime;version='[8.0.15,8.0.16)',\
|
|
||||||
org.ops4j.pax.web.pax-web-spi;version='[8.0.15,8.0.16)',\
|
|
||||||
org.ops4j.pax.web.pax-web-tomcat-common;version='[8.0.15,8.0.16)',\
|
|
||||||
org.osgi.service.component;version='[1.5.0,1.5.1)',\
|
|
||||||
junit-jupiter-api;version='[5.9.2,5.9.3)',\
|
|
||||||
junit-jupiter-engine;version='[5.9.2,5.9.3)',\
|
|
||||||
junit-platform-commons;version='[1.9.2,1.9.3)',\
|
|
||||||
junit-platform-engine;version='[1.9.2,1.9.3)',\
|
|
||||||
junit-platform-launcher;version='[1.9.2,1.9.3)',\
|
|
||||||
net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\
|
|
||||||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
|
||||||
org.eclipse.jetty.alpn.client;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.http2.client;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.http2.common;version='[9.4.50,9.4.51)',\
|
|
||||||
org.eclipse.jetty.http2.hpack;version='[9.4.50,9.4.51)',\
|
|
||||||
org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\
|
|
||||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
|
||||||
org.objenesis;version='[3.3.0,3.3.1)',\
|
|
||||||
xstream;version='[1.4.20,1.4.21)',\
|
|
||||||
org.apache.aries.spifly.dynamic.bundle;version='[1.3.6,1.3.7)',\
|
|
||||||
org.objectweb.asm.commons;version='[9.4.0,9.4.1)',\
|
|
||||||
org.objectweb.asm.tree;version='[9.4.0,9.4.1)',\
|
|
||||||
org.objectweb.asm.tree.analysis;version='[9.4.0,9.4.1)',\
|
|
||||||
org.objectweb.asm.util;version='[9.4.0,9.4.1)',\
|
|
||||||
org.openhab.binding.nest;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.binding.nest.tests;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.core;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.core.auth.oauth2client;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.core.config.core;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.core.config.discovery;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.core.io.console;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.core.io.net;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.core.test;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.core.thing;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.core.transform;version='[4.1.0,4.1.1)',\
|
|
||||||
org.openhab.base-fixes;version='[1.0.0,1.0.1)',\
|
|
||||||
javax.measure.unit-api;version='[2.2.0,2.2.1)',\
|
|
||||||
org.apiguardian.api;version='[1.1.2,1.1.3)',\
|
|
||||||
tech.units.indriya;version='[2.2.0,2.2.1)',\
|
|
||||||
uom-lib-common;version='[2.2.0,2.2.1)',\
|
|
||||||
com.fasterxml.woodstox.woodstox-core;version='[6.5.1,6.5.2)',\
|
|
||||||
org.apache.cxf.cxf-core;version='[3.6.1,3.6.2)',\
|
|
||||||
org.apache.cxf.cxf-rt-frontend-jaxrs;version='[3.6.1,3.6.2)',\
|
|
||||||
org.apache.cxf.cxf-rt-rs-client;version='[3.6.1,3.6.2)',\
|
|
||||||
org.apache.cxf.cxf-rt-rs-sse;version='[3.6.1,3.6.2)',\
|
|
||||||
org.apache.cxf.cxf-rt-security;version='[3.6.1,3.6.2)',\
|
|
||||||
org.apache.cxf.cxf-rt-transports-http;version='[3.6.1,3.6.2)',\
|
|
||||||
org.apache.ws.xmlschema.core;version='[2.3.0,2.3.1)',\
|
|
||||||
io.methvin.directory-watcher;version='[0.18.0,0.18.1)'
|
|
|
@ -1,25 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.openhab.addons.itests</groupId>
|
|
||||||
<artifactId>org.openhab.addons.reactor.itests</artifactId>
|
|
||||||
<version>4.1.0-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>org.openhab.binding.nest.tests</artifactId>
|
|
||||||
|
|
||||||
<name>openHAB Add-ons :: Integration Tests :: Nest Binding Tests</name>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
|
||||||
<artifactId>org.openhab.binding.nest</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -1,98 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.measure.Unit;
|
|
||||||
import javax.measure.quantity.Temperature;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.WWNUtils;
|
|
||||||
import org.openhab.core.library.unit.ImperialUnits;
|
|
||||||
import org.openhab.core.library.unit.SIUnits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for working with Nest test data in unit tests.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public final class WWNDataUtil {
|
|
||||||
|
|
||||||
public static final String COMPLETE_DATA_FILE_NAME = "top-level-streaming-data.json";
|
|
||||||
public static final String INCOMPLETE_DATA_FILE_NAME = "top-level-streaming-data-incomplete.json";
|
|
||||||
public static final String EMPTY_DATA_FILE_NAME = "top-level-streaming-data-empty.json";
|
|
||||||
|
|
||||||
public static final String CAMERA1_DEVICE_ID = "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ";
|
|
||||||
public static final String CAMERA1_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA";
|
|
||||||
|
|
||||||
public static final String CAMERA2_DEVICE_ID = "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ";
|
|
||||||
public static final String CAMERA2_WHERE_ID = "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ";
|
|
||||||
|
|
||||||
public static final String SMOKE1_DEVICE_ID = "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV";
|
|
||||||
public static final String SMOKE1_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg";
|
|
||||||
|
|
||||||
public static final String SMOKE2_DEVICE_ID = "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV";
|
|
||||||
public static final String SMOKE2_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA";
|
|
||||||
|
|
||||||
public static final String SMOKE3_DEVICE_ID = "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV";
|
|
||||||
public static final String SMOKE3_WHERE_ID = "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ";
|
|
||||||
|
|
||||||
public static final String SMOKE4_DEVICE_ID = "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV";
|
|
||||||
public static final String SMOKE4_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw";
|
|
||||||
|
|
||||||
public static final String STRUCTURE1_STRUCTURE_ID = "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A";
|
|
||||||
|
|
||||||
public static final String THERMOSTAT1_DEVICE_ID = "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV";
|
|
||||||
public static final String THERMOSTAT1_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw";
|
|
||||||
|
|
||||||
private WWNDataUtil() {
|
|
||||||
// Hidden utility class constructor
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Reader openDataReader(String fileName) {
|
|
||||||
String packagePath = (WWNDataUtil.class.getPackage().getName()).replaceAll("\\.", "/");
|
|
||||||
String filePath = "/" + packagePath + "/" + fileName;
|
|
||||||
InputStream inputStream = WWNDataUtil.class.getClassLoader().getResourceAsStream(filePath);
|
|
||||||
return new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T fromJson(String fileName, Class<T> dataClass) throws IOException {
|
|
||||||
try (Reader reader = openDataReader(fileName)) {
|
|
||||||
return WWNUtils.fromJson(reader, dataClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String fromFile(String fileName, Unit<Temperature> temperatureUnit) throws IOException {
|
|
||||||
String json = fromFile(fileName);
|
|
||||||
if (SIUnits.CELSIUS.equals(temperatureUnit)) {
|
|
||||||
json = json.replace("\"temperature_scale\": \"F\"", "\"temperature_scale\": \"C\"");
|
|
||||||
} else if (ImperialUnits.FAHRENHEIT.equals(temperatureUnit)) {
|
|
||||||
json = json.replace("\"temperature_scale\": \"C\"", "\"temperature_scale\": \"F\"");
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String fromFile(String fileName) throws IOException {
|
|
||||||
try (Reader reader = openDataReader(fileName)) {
|
|
||||||
return new BufferedReader(reader).lines().parallel().collect(Collectors.joining("\n"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,225 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.dto;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.openhab.core.library.unit.SIUnits;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test cases for gson parsing of model classes
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
* @author Wouter Born - Increase test coverage
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNGsonParsingTest {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNGsonParsingTest.class);
|
|
||||||
|
|
||||||
private static void assertEqualDateTime(String expected, Date actual) {
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
|
|
||||||
assertEquals(expected, sdf.format(actual));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyCompleteInput() throws IOException {
|
|
||||||
WWNTopLevelData topLevel = fromJson("top-level-data.json", WWNTopLevelData.class);
|
|
||||||
|
|
||||||
assertEquals(topLevel.getDevices().getThermostats().size(), 1);
|
|
||||||
assertNotNull(topLevel.getDevices().getThermostats().get(THERMOSTAT1_DEVICE_ID));
|
|
||||||
assertEquals(topLevel.getDevices().getCameras().size(), 2);
|
|
||||||
assertNotNull(topLevel.getDevices().getCameras().get(CAMERA1_DEVICE_ID));
|
|
||||||
assertNotNull(topLevel.getDevices().getCameras().get(CAMERA2_DEVICE_ID));
|
|
||||||
assertEquals(topLevel.getDevices().getSmokeCoAlarms().size(), 4);
|
|
||||||
assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE1_DEVICE_ID));
|
|
||||||
assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE2_DEVICE_ID));
|
|
||||||
assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE3_DEVICE_ID));
|
|
||||||
assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE4_DEVICE_ID));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyCompleteStreamingInput() throws IOException {
|
|
||||||
WWNTopLevelStreamingData topLevelStreamingData = fromJson("top-level-streaming-data.json",
|
|
||||||
WWNTopLevelStreamingData.class);
|
|
||||||
|
|
||||||
assertEquals("/", topLevelStreamingData.getPath());
|
|
||||||
|
|
||||||
WWNTopLevelData data = topLevelStreamingData.getData();
|
|
||||||
assertEquals(data.getDevices().getThermostats().size(), 1);
|
|
||||||
assertNotNull(data.getDevices().getThermostats().get(THERMOSTAT1_DEVICE_ID));
|
|
||||||
assertEquals(data.getDevices().getCameras().size(), 2);
|
|
||||||
assertNotNull(data.getDevices().getCameras().get(CAMERA1_DEVICE_ID));
|
|
||||||
assertNotNull(data.getDevices().getCameras().get(CAMERA2_DEVICE_ID));
|
|
||||||
assertEquals(data.getDevices().getSmokeCoAlarms().size(), 4);
|
|
||||||
assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE1_DEVICE_ID));
|
|
||||||
assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE2_DEVICE_ID));
|
|
||||||
assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE3_DEVICE_ID));
|
|
||||||
assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE4_DEVICE_ID));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyThermostat() throws IOException {
|
|
||||||
WWNThermostat thermostat = fromJson("thermostat-data.json", WWNThermostat.class);
|
|
||||||
logger.debug("Thermostat: {}", thermostat);
|
|
||||||
|
|
||||||
assertTrue(thermostat.isOnline());
|
|
||||||
assertTrue(thermostat.isCanHeat());
|
|
||||||
assertTrue(thermostat.isHasLeaf());
|
|
||||||
assertFalse(thermostat.isCanCool());
|
|
||||||
assertFalse(thermostat.isFanTimerActive());
|
|
||||||
assertFalse(thermostat.isLocked());
|
|
||||||
assertFalse(thermostat.isSunlightCorrectionActive());
|
|
||||||
assertTrue(thermostat.isSunlightCorrectionEnabled());
|
|
||||||
assertFalse(thermostat.isUsingEmergencyHeat());
|
|
||||||
assertEquals(THERMOSTAT1_DEVICE_ID, thermostat.getDeviceId());
|
|
||||||
assertEquals(Integer.valueOf(15), thermostat.getFanTimerDuration());
|
|
||||||
assertEqualDateTime("2017-02-02T21:00:06.000Z", thermostat.getLastConnection());
|
|
||||||
assertEqualDateTime("1970-01-01T00:00:00.000Z", thermostat.getFanTimerTimeout());
|
|
||||||
assertEquals(Double.valueOf(24.0), thermostat.getEcoTemperatureHigh());
|
|
||||||
assertEquals(Double.valueOf(12.5), thermostat.getEcoTemperatureLow());
|
|
||||||
assertEquals(Double.valueOf(22.0), thermostat.getLockedTempMax());
|
|
||||||
assertEquals(Double.valueOf(20.0), thermostat.getLockedTempMin());
|
|
||||||
assertEquals(WWNThermostat.Mode.HEAT, thermostat.getMode());
|
|
||||||
assertEquals("Living Room (Living Room)", thermostat.getName());
|
|
||||||
assertEquals("Living Room Thermostat (Living Room)", thermostat.getNameLong());
|
|
||||||
assertEquals(null, thermostat.getPreviousHvacMode());
|
|
||||||
assertEquals("5.6-7", thermostat.getSoftwareVersion());
|
|
||||||
assertEquals(WWNThermostat.State.OFF, thermostat.getHvacState());
|
|
||||||
assertEquals(STRUCTURE1_STRUCTURE_ID, thermostat.getStructureId());
|
|
||||||
assertEquals(Double.valueOf(15.5), thermostat.getTargetTemperature());
|
|
||||||
assertEquals(Double.valueOf(24.0), thermostat.getTargetTemperatureHigh());
|
|
||||||
assertEquals(Double.valueOf(20.0), thermostat.getTargetTemperatureLow());
|
|
||||||
assertEquals(SIUnits.CELSIUS, thermostat.getTemperatureUnit());
|
|
||||||
assertEquals(Integer.valueOf(0), thermostat.getTimeToTarget());
|
|
||||||
assertEquals(THERMOSTAT1_WHERE_ID, thermostat.getWhereId());
|
|
||||||
assertEquals("Living Room", thermostat.getWhereName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void thermostatTimeToTargetSupportedValueParsing() {
|
|
||||||
assertEquals((Integer) 0, WWNThermostat.parseTimeToTarget("~0"));
|
|
||||||
assertEquals((Integer) 5, WWNThermostat.parseTimeToTarget("<5"));
|
|
||||||
assertEquals((Integer) 10, WWNThermostat.parseTimeToTarget("<10"));
|
|
||||||
assertEquals((Integer) 15, WWNThermostat.parseTimeToTarget("~15"));
|
|
||||||
assertEquals((Integer) 90, WWNThermostat.parseTimeToTarget("~90"));
|
|
||||||
assertEquals((Integer) 120, WWNThermostat.parseTimeToTarget(">120"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void thermostatTimeToTargetUnsupportedValueParsing() {
|
|
||||||
assertThrows(NumberFormatException.class, () -> WWNThermostat.parseTimeToTarget("#5"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyCamera() throws IOException {
|
|
||||||
WWNCamera camera = fromJson("camera-data.json", WWNCamera.class);
|
|
||||||
logger.debug("Camera: {}", camera);
|
|
||||||
|
|
||||||
assertTrue(camera.isOnline());
|
|
||||||
assertEquals("Upstairs", camera.getName());
|
|
||||||
assertEquals("Upstairs Camera", camera.getNameLong());
|
|
||||||
assertEquals(STRUCTURE1_STRUCTURE_ID, camera.getStructureId());
|
|
||||||
assertEquals(CAMERA1_WHERE_ID, camera.getWhereId());
|
|
||||||
assertTrue(camera.isAudioInputEnabled());
|
|
||||||
assertFalse(camera.isPublicShareEnabled());
|
|
||||||
assertFalse(camera.isStreaming());
|
|
||||||
assertFalse(camera.isVideoHistoryEnabled());
|
|
||||||
assertEquals("https://camera_app_url", camera.getAppUrl());
|
|
||||||
assertEquals(CAMERA1_DEVICE_ID, camera.getDeviceId());
|
|
||||||
assertNull(camera.getLastConnection());
|
|
||||||
assertEqualDateTime("2017-01-22T08:19:20.000Z", camera.getLastIsOnlineChange());
|
|
||||||
assertNull(camera.getPublicShareUrl());
|
|
||||||
assertEquals("https://camera_snapshot_url", camera.getSnapshotUrl());
|
|
||||||
assertEquals("205-600052", camera.getSoftwareVersion());
|
|
||||||
assertEquals("https://camera_web_url", camera.getWebUrl());
|
|
||||||
assertEquals("https://last_event_animated_image_url", camera.getLastEvent().getAnimatedImageUrl());
|
|
||||||
assertEquals(2, camera.getLastEvent().getActivityZones().size());
|
|
||||||
assertEquals("id1", camera.getLastEvent().getActivityZones().get(0));
|
|
||||||
assertEquals("https://last_event_app_url", camera.getLastEvent().getAppUrl());
|
|
||||||
assertEqualDateTime("2017-01-22T07:40:38.680Z", camera.getLastEvent().getEndTime());
|
|
||||||
assertEquals("https://last_event_image_url", camera.getLastEvent().getImageUrl());
|
|
||||||
assertEqualDateTime("2017-01-22T07:40:19.020Z", camera.getLastEvent().getStartTime());
|
|
||||||
assertEqualDateTime("2017-02-05T07:40:19.020Z", camera.getLastEvent().getUrlsExpireTime());
|
|
||||||
assertEquals("https://last_event_web_url", camera.getLastEvent().getWebUrl());
|
|
||||||
assertTrue(camera.getLastEvent().isHasMotion());
|
|
||||||
assertFalse(camera.getLastEvent().isHasPerson());
|
|
||||||
assertFalse(camera.getLastEvent().isHasSound());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifySmokeDetector() throws IOException {
|
|
||||||
WWNSmokeDetector smokeDetector = fromJson("smoke-detector-data.json", WWNSmokeDetector.class);
|
|
||||||
logger.debug("SmokeDetector: {}", smokeDetector);
|
|
||||||
|
|
||||||
assertTrue(smokeDetector.isOnline());
|
|
||||||
assertEquals(SMOKE1_WHERE_ID, smokeDetector.getWhereId());
|
|
||||||
assertEquals(SMOKE1_DEVICE_ID, smokeDetector.getDeviceId());
|
|
||||||
assertEquals("Downstairs", smokeDetector.getName());
|
|
||||||
assertEquals("Downstairs Nest Protect", smokeDetector.getNameLong());
|
|
||||||
assertEqualDateTime("2017-02-02T20:53:05.338Z", smokeDetector.getLastConnection());
|
|
||||||
assertEquals(WWNSmokeDetector.BatteryHealth.OK, smokeDetector.getBatteryHealth());
|
|
||||||
assertEquals(WWNSmokeDetector.AlarmState.OK, smokeDetector.getCoAlarmState());
|
|
||||||
assertEquals(WWNSmokeDetector.AlarmState.OK, smokeDetector.getSmokeAlarmState());
|
|
||||||
assertEquals("3.1rc9", smokeDetector.getSoftwareVersion());
|
|
||||||
assertEquals(STRUCTURE1_STRUCTURE_ID, smokeDetector.getStructureId());
|
|
||||||
assertEquals(WWNSmokeDetector.UiColorState.GREEN, smokeDetector.getUiColorState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyAccessToken() throws IOException {
|
|
||||||
WWNAccessTokenData accessToken = fromJson("access-token-data.json", WWNAccessTokenData.class);
|
|
||||||
logger.debug("AccessTokenData: {}", accessToken);
|
|
||||||
|
|
||||||
assertEquals("access_token", accessToken.getAccessToken());
|
|
||||||
assertEquals(Long.valueOf(315360000L), accessToken.getExpiresIn());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyStructure() throws IOException {
|
|
||||||
WWNStructure structure = fromJson("structure-data.json", WWNStructure.class);
|
|
||||||
logger.debug("Structure: {}", structure);
|
|
||||||
|
|
||||||
assertEquals("Home", structure.getName());
|
|
||||||
assertEquals("US", structure.getCountryCode());
|
|
||||||
assertEquals("98056", structure.getPostalCode());
|
|
||||||
assertEquals(WWNStructure.HomeAwayState.HOME, structure.getAway());
|
|
||||||
assertEqualDateTime("2017-02-02T03:10:08.000Z", structure.getEtaBegin());
|
|
||||||
assertNull(structure.getEta());
|
|
||||||
assertNull(structure.getPeakPeriodEndTime());
|
|
||||||
assertNull(structure.getPeakPeriodStartTime());
|
|
||||||
assertEquals(STRUCTURE1_STRUCTURE_ID, structure.getStructureId());
|
|
||||||
assertEquals("America/Los_Angeles", structure.getTimeZone());
|
|
||||||
assertFalse(structure.isRhrEnrollment());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyError() throws IOException {
|
|
||||||
WWNErrorData error = fromJson("error-data.json", WWNErrorData.class);
|
|
||||||
logger.debug("ErrorData: {}", error);
|
|
||||||
|
|
||||||
assertEquals("blocked", error.getError());
|
|
||||||
assertEquals("https://developer.nest.com/documentation/cloud/error-messages#blocked", error.getType());
|
|
||||||
assertEquals("blocked", error.getMessage());
|
|
||||||
assertEquals("bb514046-edc9-4bca-8239-f7a3cfb0925a", error.getInstance());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
|
|
||||||
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.ArgumentCaptor;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNAccountConfiguration;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.test.WWNTestAccountHandler;
|
|
||||||
import org.openhab.core.config.core.Configuration;
|
|
||||||
import org.openhab.core.thing.Bridge;
|
|
||||||
import org.openhab.core.thing.ThingStatus;
|
|
||||||
import org.openhab.core.thing.ThingStatusInfo;
|
|
||||||
import org.openhab.core.thing.binding.ThingHandler;
|
|
||||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
|
||||||
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests cases for {@link WWNAccountHandler}.
|
|
||||||
*
|
|
||||||
* @author David Bennett - Initial contribution
|
|
||||||
*/
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNAccountHandlerTest {
|
|
||||||
|
|
||||||
private @NonNullByDefault({}) ThingHandler handler;
|
|
||||||
|
|
||||||
private @Mock @NonNullByDefault({}) Bridge bridge;
|
|
||||||
private @Mock @NonNullByDefault({}) ThingHandlerCallback callback;
|
|
||||||
private @Mock @NonNullByDefault({}) ClientBuilder clientBuilder;
|
|
||||||
private @Mock @NonNullByDefault({}) Configuration configuration;
|
|
||||||
private @Mock @NonNullByDefault({}) SseEventSourceFactory eventSourceFactory;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void beforeEach() {
|
|
||||||
handler = new WWNTestAccountHandler(bridge, clientBuilder, eventSourceFactory, "http://localhost");
|
|
||||||
handler.setCallback(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void initializeShouldCallTheCallback() {
|
|
||||||
when(bridge.getConfiguration()).thenReturn(configuration);
|
|
||||||
WWNAccountConfiguration bridgeConfig = new WWNAccountConfiguration();
|
|
||||||
when(configuration.as(eq(WWNAccountConfiguration.class))).thenReturn(bridgeConfig);
|
|
||||||
bridgeConfig.accessToken = "my token";
|
|
||||||
|
|
||||||
// we expect the handler#initialize method to call the callback during execution and
|
|
||||||
// pass it the thing and a ThingStatusInfo object containing the ThingStatus of the thing.
|
|
||||||
handler.initialize();
|
|
||||||
|
|
||||||
// the argument captor will capture the argument of type ThingStatusInfo given to the
|
|
||||||
// callback#statusUpdated method.
|
|
||||||
ArgumentCaptor<ThingStatusInfo> statusInfoCaptor = ArgumentCaptor.forClass(ThingStatusInfo.class);
|
|
||||||
|
|
||||||
// verify the interaction with the callback and capture the ThingStatusInfo argument:
|
|
||||||
verify(callback).statusUpdated(eq(bridge), statusInfoCaptor.capture());
|
|
||||||
// assert that the ThingStatusInfo given to the callback was build with the UNKNOWN status:
|
|
||||||
ThingStatusInfo thingStatusInfo = statusInfoCaptor.getValue();
|
|
||||||
assertThat(thingStatusInfo.getStatus(), is(equalTo(ThingStatus.UNKNOWN)));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.core.Is.is;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
|
|
||||||
import static org.openhab.core.library.types.OnOffType.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
|
|
||||||
import org.openhab.core.config.core.Configuration;
|
|
||||||
import org.openhab.core.library.types.StringType;
|
|
||||||
import org.openhab.core.thing.Bridge;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.openhab.core.thing.ThingStatus;
|
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
|
||||||
import org.openhab.core.thing.ThingUID;
|
|
||||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link WWNCameraHandler}.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNCameraHandlerTest extends WWNThingHandlerOSGiTest {
|
|
||||||
|
|
||||||
private static final ThingUID CAMERA_UID = new ThingUID(THING_TYPE_CAMERA, "camera1");
|
|
||||||
private static final int CHANNEL_COUNT = 20;
|
|
||||||
|
|
||||||
public WWNCameraHandlerTest() {
|
|
||||||
super(WWNCameraHandler.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Thing buildThing(Bridge bridge) {
|
|
||||||
Map<String, Object> properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, CAMERA1_DEVICE_ID);
|
|
||||||
|
|
||||||
return ThingBuilder.create(THING_TYPE_CAMERA, CAMERA_UID).withLabel("Test Camera").withBridge(bridge.getUID())
|
|
||||||
.withChannels(buildChannels(THING_TYPE_CAMERA, CAMERA_UID))
|
|
||||||
.withConfiguration(new Configuration(properties)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void completeCameraUpdate() throws IOException {
|
|
||||||
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
|
|
||||||
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
|
|
||||||
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
// Camera channel group
|
|
||||||
assertThatItemHasState(CHANNEL_CAMERA_APP_URL, new StringType("https://camera_app_url"));
|
|
||||||
assertThatItemHasState(CHANNEL_CAMERA_AUDIO_INPUT_ENABLED, ON);
|
|
||||||
assertThatItemHasState(CHANNEL_CAMERA_LAST_ONLINE_CHANGE, parseDateTimeType("2017-01-22T08:19:20.000Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_CAMERA_PUBLIC_SHARE_ENABLED, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_CAMERA_PUBLIC_SHARE_URL, new StringType("https://camera_public_share_url"));
|
|
||||||
assertThatItemHasState(CHANNEL_CAMERA_SNAPSHOT_URL, new StringType("https://camera_snapshot_url"));
|
|
||||||
assertThatItemHasState(CHANNEL_CAMERA_STREAMING, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_CAMERA_VIDEO_HISTORY_ENABLED, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_CAMERA_WEB_URL, new StringType("https://camera_web_url"));
|
|
||||||
|
|
||||||
// Last event channel group
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_ACTIVITY_ZONES, new StringType("id1,id2"));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_ANIMATED_IMAGE_URL,
|
|
||||||
new StringType("https://last_event_animated_image_url"));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_APP_URL, new StringType("https://last_event_app_url"));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_END_TIME, parseDateTimeType("2017-01-22T07:40:38.680Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_HAS_MOTION, ON);
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_HAS_PERSON, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_HAS_SOUND, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_IMAGE_URL, new StringType("https://last_event_image_url"));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_START_TIME, parseDateTimeType("2017-01-22T07:40:19.020Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_URLS_EXPIRE_TIME, parseDateTimeType("2017-02-05T07:40:19.020Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_EVENT_WEB_URL, new StringType("https://last_event_web_url"));
|
|
||||||
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void incompleteCameraUpdate() throws IOException {
|
|
||||||
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
|
|
||||||
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
|
|
||||||
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
|
|
||||||
putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.UNKNOWN)));
|
|
||||||
assertThatAllItemStatesAreNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void cameraGone() throws IOException {
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
|
|
||||||
assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void channelRefresh() throws IOException {
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
|
|
||||||
updateAllItemStatesToNull();
|
|
||||||
assertThatAllItemStatesAreNull();
|
|
||||||
|
|
||||||
refreshAllChannels();
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleStreamingCommands() throws IOException {
|
|
||||||
handleCommand(CHANNEL_CAMERA_STREAMING, ON);
|
|
||||||
assertNestApiPropertyState(CAMERA1_DEVICE_ID, "is_streaming", "true");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_CAMERA_STREAMING, OFF);
|
|
||||||
assertNestApiPropertyState(CAMERA1_DEVICE_ID, "is_streaming", "false");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_CAMERA_STREAMING, ON);
|
|
||||||
assertNestApiPropertyState(CAMERA1_DEVICE_ID, "is_streaming", "true");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.core.Is.is;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
|
|
||||||
import static org.openhab.core.library.types.OnOffType.OFF;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
|
|
||||||
import org.openhab.core.config.core.Configuration;
|
|
||||||
import org.openhab.core.library.types.StringType;
|
|
||||||
import org.openhab.core.thing.Bridge;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.openhab.core.thing.ThingStatus;
|
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
|
||||||
import org.openhab.core.thing.ThingUID;
|
|
||||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link WWNSmokeDetectorHandler}.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNSmokeDetectorHandlerTest extends WWNThingHandlerOSGiTest {
|
|
||||||
|
|
||||||
private static final ThingUID SMOKE_DETECTOR_UID = new ThingUID(THING_TYPE_SMOKE_DETECTOR, "smoke1");
|
|
||||||
private static final int CHANNEL_COUNT = 7;
|
|
||||||
|
|
||||||
public WWNSmokeDetectorHandlerTest() {
|
|
||||||
super(WWNSmokeDetectorHandler.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Thing buildThing(Bridge bridge) {
|
|
||||||
Map<String, Object> properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, SMOKE1_DEVICE_ID);
|
|
||||||
|
|
||||||
return ThingBuilder.create(THING_TYPE_SMOKE_DETECTOR, SMOKE_DETECTOR_UID).withLabel("Test Smoke Detector")
|
|
||||||
.withBridge(bridge.getUID()).withChannels(buildChannels(THING_TYPE_SMOKE_DETECTOR, SMOKE_DETECTOR_UID))
|
|
||||||
.withConfiguration(new Configuration(properties)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void completeSmokeDetectorUpdate() throws IOException {
|
|
||||||
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
|
|
||||||
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
|
|
||||||
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
assertThatItemHasState(CHANNEL_CO_ALARM_STATE, new StringType("OK"));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T20:53:05.338Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_MANUAL_TEST_TIME, parseDateTimeType("2016-10-31T23:59:59.000Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_LOW_BATTERY, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_MANUAL_TEST_ACTIVE, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_SMOKE_ALARM_STATE, new StringType("OK"));
|
|
||||||
assertThatItemHasState(CHANNEL_UI_COLOR_STATE, new StringType("GREEN"));
|
|
||||||
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void incompleteSmokeDetectorUpdate() throws IOException {
|
|
||||||
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
|
|
||||||
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
|
|
||||||
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
|
|
||||||
putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.UNKNOWN)));
|
|
||||||
assertThatAllItemStatesAreNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void smokeDetectorGone() throws IOException {
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
|
|
||||||
assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void channelRefresh() throws IOException {
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
|
|
||||||
updateAllItemStatesToNull();
|
|
||||||
assertThatAllItemStatesAreNull();
|
|
||||||
|
|
||||||
refreshAllChannels();
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,135 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.core.Is.is;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
|
|
||||||
import static org.openhab.core.library.types.OnOffType.OFF;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNStructureConfiguration;
|
|
||||||
import org.openhab.core.config.core.Configuration;
|
|
||||||
import org.openhab.core.library.types.StringType;
|
|
||||||
import org.openhab.core.thing.Bridge;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.openhab.core.thing.ThingStatus;
|
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
|
||||||
import org.openhab.core.thing.ThingUID;
|
|
||||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link WWNStructureHandler}.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNStructureHandlerTest extends WWNThingHandlerOSGiTest {
|
|
||||||
|
|
||||||
private static final ThingUID STRUCTURE_UID = new ThingUID(THING_TYPE_STRUCTURE, "structure1");
|
|
||||||
private static final int CHANNEL_COUNT = 11;
|
|
||||||
|
|
||||||
public WWNStructureHandlerTest() {
|
|
||||||
super(WWNStructureHandler.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Thing buildThing(Bridge bridge) {
|
|
||||||
Map<String, Object> properties = Map.of(WWNStructureConfiguration.STRUCTURE_ID, STRUCTURE1_STRUCTURE_ID);
|
|
||||||
|
|
||||||
return ThingBuilder.create(THING_TYPE_STRUCTURE, STRUCTURE_UID).withLabel("Test Structure")
|
|
||||||
.withBridge(bridge.getUID()).withChannels(buildChannels(THING_TYPE_STRUCTURE, STRUCTURE_UID))
|
|
||||||
.withConfiguration(new Configuration(properties)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void completeStructureUpdate() throws IOException {
|
|
||||||
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
|
|
||||||
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
|
|
||||||
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
assertThatItemHasState(CHANNEL_AWAY, new StringType("HOME"));
|
|
||||||
assertThatItemHasState(CHANNEL_CO_ALARM_STATE, new StringType("OK"));
|
|
||||||
assertThatItemHasState(CHANNEL_COUNTRY_CODE, new StringType("US"));
|
|
||||||
assertThatItemHasState(CHANNEL_ETA_BEGIN, parseDateTimeType("2017-02-02T03:10:08.000Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_PEAK_PERIOD_END_TIME, parseDateTimeType("2017-07-01T01:03:08.400Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_PEAK_PERIOD_START_TIME, parseDateTimeType("2017-06-01T13:31:10.870Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_POSTAL_CODE, new StringType("98056"));
|
|
||||||
assertThatItemHasState(CHANNEL_RUSH_HOUR_REWARDS_ENROLLMENT, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_SECURITY_STATE, new StringType("OK"));
|
|
||||||
assertThatItemHasState(CHANNEL_SMOKE_ALARM_STATE, new StringType("OK"));
|
|
||||||
assertThatItemHasState(CHANNEL_TIME_ZONE, new StringType("America/Los_Angeles"));
|
|
||||||
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void incompleteStructureUpdate() throws IOException {
|
|
||||||
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
|
|
||||||
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
|
|
||||||
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
|
|
||||||
putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
assertThatAllItemStatesAreNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void structureGone() throws IOException {
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
|
|
||||||
assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void channelRefresh() throws IOException {
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
|
|
||||||
updateAllItemStatesToNull();
|
|
||||||
assertThatAllItemStatesAreNull();
|
|
||||||
|
|
||||||
refreshAllChannels();
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleAwayCommands() throws IOException {
|
|
||||||
handleCommand(CHANNEL_AWAY, new StringType("AWAY"));
|
|
||||||
assertNestApiPropertyState(STRUCTURE1_STRUCTURE_ID, "away", "away");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_AWAY, new StringType("HOME"));
|
|
||||||
assertNestApiPropertyState(STRUCTURE1_STRUCTURE_ID, "away", "home");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_AWAY, new StringType("AWAY"));
|
|
||||||
assertNestApiPropertyState(STRUCTURE1_STRUCTURE_ID, "away", "away");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,299 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.core.Is.is;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
|
|
||||||
import static org.openhab.core.library.types.OnOffType.*;
|
|
||||||
import static org.openhab.core.library.unit.ImperialUnits.FAHRENHEIT;
|
|
||||||
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
|
|
||||||
import org.openhab.core.config.core.Configuration;
|
|
||||||
import org.openhab.core.library.types.QuantityType;
|
|
||||||
import org.openhab.core.library.types.StringType;
|
|
||||||
import org.openhab.core.library.unit.Units;
|
|
||||||
import org.openhab.core.thing.Bridge;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.openhab.core.thing.ThingStatus;
|
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
|
||||||
import org.openhab.core.thing.ThingUID;
|
|
||||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link WWNThermostatHandler}.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNThermostatHandlerTest extends WWNThingHandlerOSGiTest {
|
|
||||||
|
|
||||||
private static final ThingUID THERMOSTAT_UID = new ThingUID(THING_TYPE_THERMOSTAT, "thermostat1");
|
|
||||||
private static final int CHANNEL_COUNT = 25;
|
|
||||||
|
|
||||||
public WWNThermostatHandlerTest() {
|
|
||||||
super(WWNThermostatHandler.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Thing buildThing(Bridge bridge) {
|
|
||||||
Map<String, Object> properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, THERMOSTAT1_DEVICE_ID);
|
|
||||||
|
|
||||||
return ThingBuilder.create(THING_TYPE_THERMOSTAT, THERMOSTAT_UID).withLabel("Test Thermostat")
|
|
||||||
.withBridge(bridge.getUID()).withChannels(buildChannels(THING_TYPE_THERMOSTAT, THERMOSTAT_UID))
|
|
||||||
.withConfiguration(new Configuration(properties)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void completeThermostatCelsiusUpdate() throws IOException {
|
|
||||||
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
|
|
||||||
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
|
|
||||||
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, CELSIUS));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
assertThatItemHasState(CHANNEL_CAN_COOL, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_CAN_HEAT, ON);
|
|
||||||
assertThatItemHasState(CHANNEL_ECO_MAX_SET_POINT, new QuantityType<>(24, CELSIUS));
|
|
||||||
assertThatItemHasState(CHANNEL_ECO_MIN_SET_POINT, new QuantityType<>(12.5, CELSIUS));
|
|
||||||
assertThatItemHasState(CHANNEL_FAN_TIMER_ACTIVE, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(15, Units.MINUTE));
|
|
||||||
assertThatItemHasState(CHANNEL_FAN_TIMER_TIMEOUT, parseDateTimeType("1970-01-01T00:00:00.000Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_HAS_FAN, ON);
|
|
||||||
assertThatItemHasState(CHANNEL_HAS_LEAF, ON);
|
|
||||||
assertThatItemHasState(CHANNEL_HUMIDITY, new QuantityType<>(25, Units.PERCENT));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T21:00:06.000Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_LOCKED, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_LOCKED_MAX_SET_POINT, new QuantityType<>(22, CELSIUS));
|
|
||||||
assertThatItemHasState(CHANNEL_LOCKED_MIN_SET_POINT, new QuantityType<>(20, CELSIUS));
|
|
||||||
assertThatItemHasState(CHANNEL_MAX_SET_POINT, new QuantityType<>(24, CELSIUS));
|
|
||||||
assertThatItemHasState(CHANNEL_MIN_SET_POINT, new QuantityType<>(20, CELSIUS));
|
|
||||||
assertThatItemHasState(CHANNEL_MODE, new StringType("HEAT"));
|
|
||||||
assertThatItemHasState(CHANNEL_PREVIOUS_MODE, new StringType("HEAT"));
|
|
||||||
assertThatItemHasState(CHANNEL_SET_POINT, new QuantityType<>(15.5, CELSIUS));
|
|
||||||
assertThatItemHasState(CHANNEL_STATE, new StringType("OFF"));
|
|
||||||
assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ACTIVE, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ENABLED, ON);
|
|
||||||
assertThatItemHasState(CHANNEL_TEMPERATURE, new QuantityType<>(19, CELSIUS));
|
|
||||||
assertThatItemHasState(CHANNEL_TIME_TO_TARGET, new QuantityType<>(0, Units.MINUTE));
|
|
||||||
assertThatItemHasState(CHANNEL_USING_EMERGENCY_HEAT, OFF);
|
|
||||||
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void completeThermostatFahrenheitUpdate() throws IOException {
|
|
||||||
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
|
|
||||||
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
|
|
||||||
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, FAHRENHEIT));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
assertThatItemHasState(CHANNEL_CAN_COOL, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_CAN_HEAT, ON);
|
|
||||||
assertThatItemHasState(CHANNEL_ECO_MAX_SET_POINT, new QuantityType<>(76, FAHRENHEIT));
|
|
||||||
assertThatItemHasState(CHANNEL_ECO_MIN_SET_POINT, new QuantityType<>(55, FAHRENHEIT));
|
|
||||||
assertThatItemHasState(CHANNEL_FAN_TIMER_ACTIVE, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(15, Units.MINUTE));
|
|
||||||
assertThatItemHasState(CHANNEL_FAN_TIMER_TIMEOUT, parseDateTimeType("1970-01-01T00:00:00.000Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_HAS_FAN, ON);
|
|
||||||
assertThatItemHasState(CHANNEL_HAS_LEAF, ON);
|
|
||||||
assertThatItemHasState(CHANNEL_HUMIDITY, new QuantityType<>(25, Units.PERCENT));
|
|
||||||
assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T21:00:06.000Z"));
|
|
||||||
assertThatItemHasState(CHANNEL_LOCKED, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_LOCKED_MAX_SET_POINT, new QuantityType<>(72, FAHRENHEIT));
|
|
||||||
assertThatItemHasState(CHANNEL_LOCKED_MIN_SET_POINT, new QuantityType<>(68, FAHRENHEIT));
|
|
||||||
assertThatItemHasState(CHANNEL_MAX_SET_POINT, new QuantityType<>(75, FAHRENHEIT));
|
|
||||||
assertThatItemHasState(CHANNEL_MIN_SET_POINT, new QuantityType<>(68, FAHRENHEIT));
|
|
||||||
assertThatItemHasState(CHANNEL_MODE, new StringType("HEAT"));
|
|
||||||
assertThatItemHasState(CHANNEL_PREVIOUS_MODE, new StringType("HEAT"));
|
|
||||||
assertThatItemHasState(CHANNEL_SET_POINT, new QuantityType<>(60, FAHRENHEIT));
|
|
||||||
assertThatItemHasState(CHANNEL_STATE, new StringType("OFF"));
|
|
||||||
assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ACTIVE, OFF);
|
|
||||||
assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ENABLED, ON);
|
|
||||||
assertThatItemHasState(CHANNEL_TEMPERATURE, new QuantityType<>(66, FAHRENHEIT));
|
|
||||||
assertThatItemHasState(CHANNEL_TIME_TO_TARGET, new QuantityType<>(0, Units.MINUTE));
|
|
||||||
assertThatItemHasState(CHANNEL_USING_EMERGENCY_HEAT, OFF);
|
|
||||||
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void incompleteThermostatUpdate() throws IOException {
|
|
||||||
assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
|
|
||||||
assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
|
|
||||||
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
|
|
||||||
putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.UNKNOWN)));
|
|
||||||
assertThatAllItemStatesAreNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void thermostatGone() throws IOException {
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
|
|
||||||
assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void channelRefresh() throws IOException {
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
|
|
||||||
updateAllItemStatesToNull();
|
|
||||||
assertThatAllItemStatesAreNull();
|
|
||||||
|
|
||||||
refreshAllChannels();
|
|
||||||
assertThatAllItemStatesAreNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleFanTimerActiveCommands() throws IOException {
|
|
||||||
handleCommand(CHANNEL_FAN_TIMER_ACTIVE, ON);
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "true");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_FAN_TIMER_ACTIVE, OFF);
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "false");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_FAN_TIMER_ACTIVE, ON);
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleFanTimerDurationCommands() throws IOException {
|
|
||||||
int[] durations = { 15, 30, 45, 60, 120, 240, 480, 960, 15 };
|
|
||||||
for (int duration : durations) {
|
|
||||||
handleCommand(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(duration, Units.MINUTE));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_duration", String.valueOf(duration));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleMaxSetPointCelsiusCommands() throws IOException {
|
|
||||||
celsiusCommandsTest(CHANNEL_MAX_SET_POINT, "target_temperature_high_c");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleMaxSetPointFahrenheitCommands() throws IOException {
|
|
||||||
fahrenheitCommandsTest(CHANNEL_MAX_SET_POINT, "target_temperature_high_f");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleMinSetPointCelsiusCommands() throws IOException {
|
|
||||||
celsiusCommandsTest(CHANNEL_MIN_SET_POINT, "target_temperature_low_c");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleMinSetPointFahrenheitCommands() throws IOException {
|
|
||||||
fahrenheitCommandsTest(CHANNEL_MIN_SET_POINT, "target_temperature_low_f");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleChannelModeCommands() throws IOException {
|
|
||||||
handleCommand(CHANNEL_MODE, new StringType("HEAT"));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_MODE, new StringType("COOL"));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "cool");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_MODE, new StringType("HEAT_COOL"));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat-cool");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_MODE, new StringType("ECO"));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "eco");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_MODE, new StringType("OFF"));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "off");
|
|
||||||
|
|
||||||
handleCommand(CHANNEL_MODE, new StringType("HEAT"));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleSetPointCelsiusCommands() throws IOException {
|
|
||||||
celsiusCommandsTest(CHANNEL_SET_POINT, "target_temperature_c");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleSetPointFahrenheitCommands() throws IOException {
|
|
||||||
fahrenheitCommandsTest(CHANNEL_SET_POINT, "target_temperature_f");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void celsiusCommandsTest(String channelId, String apiPropertyName) throws IOException {
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, CELSIUS));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(20, CELSIUS));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "20.0");
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(21.123, CELSIUS));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "21.0");
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(22.541, CELSIUS));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "22.5");
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(23.74, CELSIUS));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "23.5");
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(23.75, CELSIUS));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "24.0");
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(70, FAHRENHEIT));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "21.0");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fahrenheitCommandsTest(String channelId, String apiPropertyName) throws IOException {
|
|
||||||
waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, FAHRENHEIT));
|
|
||||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(70, FAHRENHEIT));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "70");
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(71.123, FAHRENHEIT));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "71");
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(71.541, FAHRENHEIT));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "72");
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(72.74, FAHRENHEIT));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "73");
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(73.75, FAHRENHEIT));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "74");
|
|
||||||
|
|
||||||
handleCommand(channelId, new QuantityType<>(21, CELSIUS));
|
|
||||||
assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "70");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,367 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.handler;
|
|
||||||
|
|
||||||
import static java.util.Map.entry;
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.core.Is.is;
|
|
||||||
import static org.hamcrest.core.IsNot.not;
|
|
||||||
import static org.mockito.ArgumentMatchers.nullable;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient.PUT;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.format.DateTimeParseException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.mockito.ArgumentMatchers;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.config.WWNAccountConfiguration;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.test.WWNTestAccountHandler;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.test.WWNTestApiServlet;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.test.WWNTestHandlerFactory;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.test.WWNTestServer;
|
|
||||||
import org.openhab.core.config.core.Configuration;
|
|
||||||
import org.openhab.core.events.EventPublisher;
|
|
||||||
import org.openhab.core.items.Item;
|
|
||||||
import org.openhab.core.items.ItemFactory;
|
|
||||||
import org.openhab.core.items.ItemNotFoundException;
|
|
||||||
import org.openhab.core.items.ItemRegistry;
|
|
||||||
import org.openhab.core.items.events.ItemEventFactory;
|
|
||||||
import org.openhab.core.library.types.DateTimeType;
|
|
||||||
import org.openhab.core.test.TestPortUtil;
|
|
||||||
import org.openhab.core.test.java.JavaOSGiTest;
|
|
||||||
import org.openhab.core.test.storage.VolatileStorageService;
|
|
||||||
import org.openhab.core.thing.Bridge;
|
|
||||||
import org.openhab.core.thing.Channel;
|
|
||||||
import org.openhab.core.thing.ChannelUID;
|
|
||||||
import org.openhab.core.thing.ManagedThingProvider;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.openhab.core.thing.ThingProvider;
|
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
|
||||||
import org.openhab.core.thing.ThingUID;
|
|
||||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
|
||||||
import org.openhab.core.thing.binding.ThingTypeProvider;
|
|
||||||
import org.openhab.core.thing.binding.builder.BridgeBuilder;
|
|
||||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
|
||||||
import org.openhab.core.thing.link.ItemChannelLink;
|
|
||||||
import org.openhab.core.thing.link.ManagedItemChannelLinkProvider;
|
|
||||||
import org.openhab.core.thing.type.ChannelDefinition;
|
|
||||||
import org.openhab.core.thing.type.ChannelGroupDefinition;
|
|
||||||
import org.openhab.core.thing.type.ChannelGroupType;
|
|
||||||
import org.openhab.core.thing.type.ChannelGroupTypeRegistry;
|
|
||||||
import org.openhab.core.thing.type.ChannelType;
|
|
||||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
|
||||||
import org.openhab.core.thing.type.ThingType;
|
|
||||||
import org.openhab.core.thing.type.ThingTypeRegistry;
|
|
||||||
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.osgi.service.component.ComponentContext;
|
|
||||||
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link WWNThingHandlerOSGiTest} is an abstract base class for Nest OSGi based tests.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public abstract class WWNThingHandlerOSGiTest extends JavaOSGiTest {
|
|
||||||
|
|
||||||
private static final String SERVER_HOST = "127.0.0.1";
|
|
||||||
private static final int SERVER_PORT = TestPortUtil.findFreePort();
|
|
||||||
private static final int SERVER_TIMEOUT = -1;
|
|
||||||
private static final String REDIRECT_URL = "http://" + SERVER_HOST + ":" + SERVER_PORT;
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNThingHandlerOSGiTest.class);
|
|
||||||
|
|
||||||
private static @Nullable WWNTestServer server;
|
|
||||||
private static WWNTestApiServlet servlet = new WWNTestApiServlet();
|
|
||||||
|
|
||||||
private @NonNullByDefault({}) ChannelTypeRegistry channelTypeRegistry;
|
|
||||||
private @NonNullByDefault({}) ChannelGroupTypeRegistry channelGroupTypeRegistry;
|
|
||||||
private @NonNullByDefault({}) ItemFactory itemFactory;
|
|
||||||
private @NonNullByDefault({}) ItemRegistry itemRegistry;
|
|
||||||
private @NonNullByDefault({}) EventPublisher eventPublisher;
|
|
||||||
private @NonNullByDefault({}) ManagedThingProvider managedThingProvider;
|
|
||||||
private @NonNullByDefault({}) ThingTypeRegistry thingTypeRegistry;
|
|
||||||
private @NonNullByDefault({}) ManagedItemChannelLinkProvider managedItemChannelLinkProvider;
|
|
||||||
private @NonNullByDefault({}) VolatileStorageService volatileStorageService = new VolatileStorageService();
|
|
||||||
|
|
||||||
protected @NonNullByDefault({}) Bridge bridge;
|
|
||||||
protected @NonNullByDefault({}) WWNTestAccountHandler bridgeHandler;
|
|
||||||
protected @NonNullByDefault({}) Thing thing;
|
|
||||||
protected @NonNullByDefault({}) WWNBaseHandler<?> thingHandler;
|
|
||||||
private Class<? extends WWNBaseHandler<?>> thingClass;
|
|
||||||
|
|
||||||
private @NonNullByDefault({}) WWNTestHandlerFactory nestTestHandlerFactory;
|
|
||||||
private @NonNullByDefault({}) ClientBuilder clientBuilder;
|
|
||||||
private @NonNullByDefault({}) SseEventSourceFactory eventSourceFactory;
|
|
||||||
|
|
||||||
public WWNThingHandlerOSGiTest(Class<? extends WWNBaseHandler<?>> thingClass) {
|
|
||||||
this.thingClass = thingClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void setUpClass() throws Exception {
|
|
||||||
ServletHolder holder = new ServletHolder(servlet);
|
|
||||||
server = new WWNTestServer(SERVER_HOST, SERVER_PORT, SERVER_TIMEOUT, holder);
|
|
||||||
server.startServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterAll
|
|
||||||
public static void tearDownClass() throws Exception {
|
|
||||||
WWNTestServer testServer = server;
|
|
||||||
if (testServer != null) {
|
|
||||||
testServer.stopServer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setUp() throws ItemNotFoundException {
|
|
||||||
registerService(volatileStorageService);
|
|
||||||
|
|
||||||
managedThingProvider = Objects.requireNonNull(getService(ThingProvider.class, ManagedThingProvider.class),
|
|
||||||
"Could not get ManagedThingProvider");
|
|
||||||
thingTypeRegistry = Objects.requireNonNull(getService(ThingTypeRegistry.class),
|
|
||||||
"Could not get ThingTypeRegistry");
|
|
||||||
channelTypeRegistry = Objects.requireNonNull(getService(ChannelTypeRegistry.class),
|
|
||||||
"Could not get ChannelTypeRegistry");
|
|
||||||
channelGroupTypeRegistry = Objects.requireNonNull(getService(ChannelGroupTypeRegistry.class),
|
|
||||||
"Could not get ChannelGroupTypeRegistry");
|
|
||||||
eventPublisher = Objects.requireNonNull(getService(EventPublisher.class), "Could not get EventPublisher");
|
|
||||||
itemFactory = Objects.requireNonNull(getService(ItemFactory.class), "Could not get ItemFactory");
|
|
||||||
itemRegistry = Objects.requireNonNull(getService(ItemRegistry.class), "Could not get ItemRegistry");
|
|
||||||
managedItemChannelLinkProvider = Objects.requireNonNull(getService(ManagedItemChannelLinkProvider.class),
|
|
||||||
"Could not get ManagedItemChannelLinkProvider");
|
|
||||||
clientBuilder = Objects.requireNonNull(getService(ClientBuilder.class), "Could not get ClientBuilder");
|
|
||||||
eventSourceFactory = Objects.requireNonNull(getService(SseEventSourceFactory.class),
|
|
||||||
"Could not get SseEventSourceFactory");
|
|
||||||
|
|
||||||
ComponentContext componentContext = mock(ComponentContext.class);
|
|
||||||
when(componentContext.getBundleContext()).thenReturn(bundleContext);
|
|
||||||
|
|
||||||
nestTestHandlerFactory = new WWNTestHandlerFactory(clientBuilder, eventSourceFactory);
|
|
||||||
nestTestHandlerFactory.activate(componentContext,
|
|
||||||
Map.of(WWNTestHandlerFactory.REDIRECT_URL_CONFIG_PROPERTY, REDIRECT_URL));
|
|
||||||
registerService(nestTestHandlerFactory);
|
|
||||||
|
|
||||||
ThingTypeProvider thingTypeProvider = mock(ThingTypeProvider.class);
|
|
||||||
when(thingTypeProvider.getThingType(ArgumentMatchers.any(ThingTypeUID.class), nullable(Locale.class)))
|
|
||||||
.thenReturn(mock(ThingType.class));
|
|
||||||
registerService(thingTypeProvider);
|
|
||||||
|
|
||||||
nestTestHandlerFactory = Objects.requireNonNull(
|
|
||||||
getService(ThingHandlerFactory.class, WWNTestHandlerFactory.class),
|
|
||||||
"Could not get NestTestHandlerFactory");
|
|
||||||
|
|
||||||
bridge = buildBridge();
|
|
||||||
thing = buildThing(bridge);
|
|
||||||
|
|
||||||
bridgeHandler = addThing(bridge, WWNTestAccountHandler.class);
|
|
||||||
thingHandler = addThing(thing, thingClass);
|
|
||||||
|
|
||||||
createAndLinkItems();
|
|
||||||
assertThatAllItemStatesAreNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
public void tearDown() {
|
|
||||||
servlet.reset();
|
|
||||||
servlet.closeConnections();
|
|
||||||
|
|
||||||
if (thing != null) {
|
|
||||||
managedThingProvider.remove(thing.getUID());
|
|
||||||
}
|
|
||||||
if (bridge != null) {
|
|
||||||
managedThingProvider.remove(bridge.getUID());
|
|
||||||
}
|
|
||||||
|
|
||||||
unregisterService(volatileStorageService);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Bridge buildBridge() {
|
|
||||||
Map<String, Object> properties = Map.ofEntries( //
|
|
||||||
entry(WWNAccountConfiguration.ACCESS_TOKEN,
|
|
||||||
"c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc"),
|
|
||||||
entry(WWNAccountConfiguration.PINCODE, "64P2XRYT"),
|
|
||||||
entry(WWNAccountConfiguration.PRODUCT_ID, "8fdf9885-ca07-4252-1aa3-f3d5ca9589e0"),
|
|
||||||
entry(WWNAccountConfiguration.PRODUCT_SECRET, "QITLR3iyUlWaj9dbvCxsCKp4f"));
|
|
||||||
|
|
||||||
return BridgeBuilder.create(WWNTestAccountHandler.THING_TYPE_TEST_BRIDGE, "test_account")
|
|
||||||
.withLabel("Test Account").withConfiguration(new Configuration(properties)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract Thing buildThing(Bridge bridge);
|
|
||||||
|
|
||||||
protected List<Channel> buildChannels(ThingTypeUID thingTypeUID, ThingUID thingUID) {
|
|
||||||
waitForAssert(() -> assertThat(thingTypeRegistry.getThingType(thingTypeUID), notNullValue()));
|
|
||||||
|
|
||||||
ThingType thingType = thingTypeRegistry.getThingType(thingTypeUID);
|
|
||||||
|
|
||||||
List<Channel> channels = new ArrayList<>(buildChannels(thingUID, thingType.getChannelDefinitions(), id -> id));
|
|
||||||
for (ChannelGroupDefinition channelGroupDefinition : thingType.getChannelGroupDefinitions()) {
|
|
||||||
ChannelGroupType channelGroupType = channelGroupTypeRegistry
|
|
||||||
.getChannelGroupType(channelGroupDefinition.getTypeUID());
|
|
||||||
String groupId = channelGroupDefinition.getId();
|
|
||||||
if (channelGroupType != null) {
|
|
||||||
channels.addAll(
|
|
||||||
buildChannels(thingUID, channelGroupType.getChannelDefinitions(), id -> groupId + "#" + id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
channels.sort((Channel c1, Channel c2) -> c1.getUID().getId().compareTo(c2.getUID().getId()));
|
|
||||||
return channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Channel> buildChannels(ThingUID thingUID, List<ChannelDefinition> channelDefinitions,
|
|
||||||
Function<String, String> channelIdFunction) {
|
|
||||||
List<Channel> result = new ArrayList<>();
|
|
||||||
for (ChannelDefinition channelDefinition : channelDefinitions) {
|
|
||||||
ChannelType channelType = channelTypeRegistry.getChannelType(channelDefinition.getChannelTypeUID());
|
|
||||||
if (channelType != null) {
|
|
||||||
result.add(ChannelBuilder
|
|
||||||
.create(new ChannelUID(thingUID, channelIdFunction.apply(channelDefinition.getId())),
|
|
||||||
channelType.getItemType())
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected <T> T addThing(Thing thing, Class<T> thingHandlerClass) {
|
|
||||||
assertThat(thing.getHandler(), is(nullValue()));
|
|
||||||
managedThingProvider.add(thing);
|
|
||||||
waitForAssert(() -> assertThat(thing.getHandler(), notNullValue()));
|
|
||||||
assertThat(thing.getConfiguration(), is(notNullValue()));
|
|
||||||
assertThat(thing.getHandler(), is(instanceOf(thingHandlerClass)));
|
|
||||||
return (T) thing.getHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getThingId() {
|
|
||||||
return thing.getUID().getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ThingUID getThingUID() {
|
|
||||||
return thing.getUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void putStreamingEventData(String json) throws IOException {
|
|
||||||
String singleLineJson = json.replaceAll("\n\r\\s+", "").replaceAll("\n\\s+", "").replaceAll("\n\r", "")
|
|
||||||
.replaceAll("\n", "");
|
|
||||||
servlet.queueEvent(PUT, singleLineJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void createAndLinkItems() {
|
|
||||||
thing.getChannels().forEach(c -> {
|
|
||||||
String itemName = getItemName(c.getUID().getId());
|
|
||||||
Item item = itemFactory.createItem(c.getAcceptedItemType(), itemName);
|
|
||||||
if (item != null) {
|
|
||||||
itemRegistry.add(item);
|
|
||||||
}
|
|
||||||
managedItemChannelLinkProvider.add(new ItemChannelLink(itemName, c.getUID()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertThatItemHasState(String channelId, State state) {
|
|
||||||
waitForAssert(() -> assertThat("Wrong state for item of channel '" + channelId + "' ", getItemState(channelId),
|
|
||||||
is(state)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertThatItemHasNotState(String channelId, State state) {
|
|
||||||
waitForAssert(() -> assertThat("Wrong state for item of channel '" + channelId + "' ", getItemState(channelId),
|
|
||||||
is(not(state))));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertThatAllItemStatesAreNull() {
|
|
||||||
thing.getChannels().forEach(c -> assertThatItemHasState(c.getUID().getId(), UnDefType.NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertThatAllItemStatesAreNotNull() {
|
|
||||||
thing.getChannels().forEach(c -> assertThatItemHasNotState(c.getUID().getId(), UnDefType.NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ChannelUID getChannelUID(String channelId) {
|
|
||||||
return new ChannelUID(getThingUID(), channelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getItemName(String channelId) {
|
|
||||||
return getThingId() + "_" + channelId.replaceAll("#", "_");
|
|
||||||
}
|
|
||||||
|
|
||||||
private State getItemState(String channelId) {
|
|
||||||
String itemName = getItemName(channelId);
|
|
||||||
try {
|
|
||||||
return itemRegistry.getItem(itemName).getState();
|
|
||||||
} catch (ItemNotFoundException e) {
|
|
||||||
throw new AssertionError("Item with name '" + itemName + "' not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void logItemStates() {
|
|
||||||
thing.getChannels().forEach(c -> {
|
|
||||||
String channelId = c.getUID().getId();
|
|
||||||
String itemName = getItemName(channelId);
|
|
||||||
logger.debug("{} = {}", itemName, getItemState(channelId));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateAllItemStatesToNull() {
|
|
||||||
thing.getChannels().forEach(c -> updateItemState(c.getUID().getId(), UnDefType.NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void refreshAllChannels() {
|
|
||||||
thing.getChannels().forEach(c -> thingHandler.handleCommand(c.getUID(), RefreshType.REFRESH));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleCommand(String channelId, Command command) {
|
|
||||||
thingHandler.handleCommand(getChannelUID(channelId), command);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateItemState(String channelId, State state) {
|
|
||||||
String itemName = getItemName(channelId);
|
|
||||||
eventPublisher.post(ItemEventFactory.createStateEvent(itemName, state));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertNestApiPropertyState(String nestId, String propertyName, String state) {
|
|
||||||
waitForAssert(() -> assertThat(servlet.getNestIdPropertyState(nestId, propertyName), is(state)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DateTimeType parseDateTimeType(String text) {
|
|
||||||
try {
|
|
||||||
return new DateTimeType(Instant.parse(text).atZone(TimeZone.getDefault().toZoneId()));
|
|
||||||
} catch (DateTimeParseException e) {
|
|
||||||
throw new IllegalArgumentException("Invalid date time argument: " + text, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.test;
|
|
||||||
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.BINDING_ID;
|
|
||||||
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.exceptions.InvalidWWNAccessTokenException;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.handler.WWNAccountHandler;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.handler.WWNRedirectUrlSupplier;
|
|
||||||
import org.openhab.core.thing.Bridge;
|
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
|
||||||
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link WWNTestAccountHandler} is a {@link WWNAccountHandler} modified for testing. Using the
|
|
||||||
* {@link NestTestRedirectUrlSupplier} it will always connect to same provided {@link #redirectUrl}.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNTestAccountHandler extends WWNAccountHandler {
|
|
||||||
|
|
||||||
class NestTestRedirectUrlSupplier extends WWNRedirectUrlSupplier {
|
|
||||||
|
|
||||||
NestTestRedirectUrlSupplier(Properties httpHeaders) {
|
|
||||||
super(httpHeaders);
|
|
||||||
this.cachedUrl = redirectUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resetCache() {
|
|
||||||
// Skip resetting the URL so the test server keeps being used
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final ThingTypeUID THING_TYPE_TEST_BRIDGE = new ThingTypeUID(BINDING_ID, "wwn_test_account");
|
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_TEST_BRIDGE);
|
|
||||||
|
|
||||||
private String redirectUrl;
|
|
||||||
|
|
||||||
public WWNTestAccountHandler(Bridge bridge, ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory,
|
|
||||||
String redirectUrl) {
|
|
||||||
super(bridge, clientBuilder, eventSourceFactory);
|
|
||||||
this.redirectUrl = redirectUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected WWNRedirectUrlSupplier createRedirectUrlSupplier() throws InvalidWWNAccessTokenException {
|
|
||||||
return new NestTestRedirectUrlSupplier(getHttpHeaders());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,223 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.test;
|
|
||||||
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
|
|
||||||
import static org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
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.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link WWNTestApiServlet} mocks the Nest API during tests.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNTestApiServlet extends HttpServlet {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -5414910055159062745L;
|
|
||||||
|
|
||||||
private static final String NEW_LINE = "\n";
|
|
||||||
|
|
||||||
private static final String UPDATE_PATHS[] = { NEST_CAMERA_UPDATE_PATH, NEST_SMOKE_ALARM_UPDATE_PATH,
|
|
||||||
NEST_STRUCTURE_UPDATE_PATH, NEST_THERMOSTAT_UPDATE_PATH };
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(WWNTestApiServlet.class);
|
|
||||||
|
|
||||||
private static class SseEvent {
|
|
||||||
private String name;
|
|
||||||
private @Nullable String data;
|
|
||||||
|
|
||||||
public SseEvent(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SseEvent(String name, String data) {
|
|
||||||
this.name = name;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable String getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, Map<String, String>> nestIdPropertiesMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private final Map<Thread, Queue<SseEvent>> listenerQueues = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private final ThreadLocal<PrintWriter> threadLocalWriter = new ThreadLocal<>();
|
|
||||||
|
|
||||||
private final Gson gson = new GsonBuilder().create();
|
|
||||||
|
|
||||||
public void closeConnections() {
|
|
||||||
Set<Thread> threads = listenerQueues.keySet();
|
|
||||||
listenerQueues.clear();
|
|
||||||
threads.forEach(Thread::interrupt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
nestIdPropertiesMap.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void queueEvent(String eventName) {
|
|
||||||
SseEvent event = new SseEvent(eventName);
|
|
||||||
listenerQueues.forEach((thread, queue) -> queue.add(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void queueEvent(String eventName, String data) {
|
|
||||||
SseEvent event = new SseEvent(eventName, data);
|
|
||||||
listenerQueues.forEach((thread, queue) -> queue.add(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
private void writeEvent(SseEvent event) {
|
|
||||||
logger.debug("Writing {} event", event.getName());
|
|
||||||
|
|
||||||
PrintWriter writer = threadLocalWriter.get();
|
|
||||||
|
|
||||||
writer.write("event: ");
|
|
||||||
writer.write(event.getName());
|
|
||||||
writer.write(NEW_LINE);
|
|
||||||
|
|
||||||
String eventData = event.getData();
|
|
||||||
if (eventData != null) {
|
|
||||||
for (String dataLine : eventData.split(NEW_LINE)) {
|
|
||||||
writer.write("data: ");
|
|
||||||
writer.write(dataLine);
|
|
||||||
writer.write(NEW_LINE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.write(NEW_LINE);
|
|
||||||
writer.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeEvent(String eventName) {
|
|
||||||
writeEvent(new SseEvent(eventName));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
|
||||||
ArrayBlockingQueue<SseEvent> queue = new ArrayBlockingQueue<>(10);
|
|
||||||
listenerQueues.put(Thread.currentThread(), queue);
|
|
||||||
|
|
||||||
response.setContentType("text/event-stream");
|
|
||||||
response.setCharacterEncoding("UTF-8");
|
|
||||||
response.flushBuffer();
|
|
||||||
|
|
||||||
logger.debug("Opened event stream to {}:{}", request.getRemoteHost(), request.getRemotePort());
|
|
||||||
|
|
||||||
PrintWriter writer = response.getWriter();
|
|
||||||
threadLocalWriter.set(writer);
|
|
||||||
writeEvent(OPEN);
|
|
||||||
|
|
||||||
while (listenerQueues.containsKey(Thread.currentThread()) && !writer.checkError()) {
|
|
||||||
try {
|
|
||||||
SseEvent event = queue.poll(KEEP_ALIVE_MILLIS, TimeUnit.MILLISECONDS);
|
|
||||||
if (event != null) {
|
|
||||||
writeEvent(event);
|
|
||||||
} else {
|
|
||||||
writeEvent(KEEP_ALIVE);
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
logger.debug("Evaluating loop conditions after interrupt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listenerQueues.remove(Thread.currentThread());
|
|
||||||
threadLocalWriter.remove();
|
|
||||||
writer.close();
|
|
||||||
|
|
||||||
logger.debug("Closed event stream to {}:{}", request.getRemoteHost(), request.getRemotePort());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doPut(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
logger.debug("Received put request: {}", request);
|
|
||||||
|
|
||||||
String uri = request.getRequestURI();
|
|
||||||
String nestId = getNestIdFromURI(uri);
|
|
||||||
|
|
||||||
if (nestId == null) {
|
|
||||||
logger.error("Unsupported URI: {}", uri);
|
|
||||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStreamReader reader = new InputStreamReader(request.getInputStream());
|
|
||||||
Map<String, String> propertiesUpdate = gson.fromJson(reader, new TypeToken<Map<String, String>>() {
|
|
||||||
}.getType());
|
|
||||||
|
|
||||||
Map<String, String> properties = getOrCreateProperties(nestId);
|
|
||||||
properties.putAll(propertiesUpdate);
|
|
||||||
|
|
||||||
gson.toJson(propertiesUpdate, response.getWriter());
|
|
||||||
|
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable String getNestIdFromURI(@Nullable String uri) {
|
|
||||||
if (uri == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (String updatePath : UPDATE_PATHS) {
|
|
||||||
if (uri.startsWith(updatePath)) {
|
|
||||||
return uri.replaceAll(updatePath, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> getOrCreateProperties(String nestId) {
|
|
||||||
Map<String, String> properties = nestIdPropertiesMap.get(nestId);
|
|
||||||
if (properties == null) {
|
|
||||||
properties = new HashMap<>();
|
|
||||||
nestIdPropertiesMap.put(nestId, properties);
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable String getNestIdPropertyState(String nestId, String propertyName) {
|
|
||||||
Map<String, String> properties = nestIdPropertiesMap.get(nestId);
|
|
||||||
return properties == null ? null : properties.get(propertyName);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.test;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Hashtable;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.discovery.WWNDiscoveryService;
|
|
||||||
import org.openhab.binding.nest.internal.wwn.handler.WWNAccountHandler;
|
|
||||||
import org.openhab.core.config.discovery.DiscoveryService;
|
|
||||||
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.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.Modified;
|
|
||||||
import org.osgi.service.component.annotations.Reference;
|
|
||||||
import org.osgi.service.jaxrs.client.SseEventSourceFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link WWNTestHandlerFactory} is responsible for creating test things and thing handlers.
|
|
||||||
*
|
|
||||||
* @author Wouter Born - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNTestHandlerFactory extends BaseThingHandlerFactory implements ThingHandlerFactory {
|
|
||||||
|
|
||||||
public static final String REDIRECT_URL_CONFIG_PROPERTY = "redirect.url";
|
|
||||||
|
|
||||||
private final ClientBuilder clientBuilder;
|
|
||||||
private final SseEventSourceFactory eventSourceFactory;
|
|
||||||
private final Map<ThingUID, ServiceRegistration<?>> discoveryService = new HashMap<>();
|
|
||||||
|
|
||||||
private String redirectUrl = "http://localhost";
|
|
||||||
|
|
||||||
@Activate
|
|
||||||
public WWNTestHandlerFactory(@Reference ClientBuilder clientBuilder,
|
|
||||||
@Reference SseEventSourceFactory eventSourceFactory) {
|
|
||||||
this.clientBuilder = clientBuilder;
|
|
||||||
this.eventSourceFactory = eventSourceFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
|
||||||
return WWNTestAccountHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Activate
|
|
||||||
public void activate(ComponentContext componentContext, Map<String, Object> config) {
|
|
||||||
super.activate(componentContext);
|
|
||||||
modified(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Modified
|
|
||||||
public void modified(Map<String, Object> config) {
|
|
||||||
String url = (String) config.get(REDIRECT_URL_CONFIG_PROPERTY);
|
|
||||||
if (url != null) {
|
|
||||||
this.redirectUrl = url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
|
||||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
|
||||||
if (thingTypeUID.equals(WWNTestAccountHandler.THING_TYPE_TEST_BRIDGE)) {
|
|
||||||
WWNTestAccountHandler handler = new WWNTestAccountHandler((Bridge) thing, clientBuilder, eventSourceFactory,
|
|
||||||
redirectUrl);
|
|
||||||
WWNDiscoveryService service = new WWNDiscoveryService();
|
|
||||||
service.setThingHandler(handler);
|
|
||||||
// Register the discovery service.
|
|
||||||
discoveryService.put(handler.getThing().getUID(),
|
|
||||||
bundleContext.registerService(DiscoveryService.class.getName(), service, new Hashtable<>()));
|
|
||||||
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the handler for the specific thing. This also handles disabling the discovery
|
|
||||||
* service when the bridge is removed.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void removeHandler(ThingHandler thingHandler) {
|
|
||||||
if (thingHandler instanceof WWNAccountHandler) {
|
|
||||||
ServiceRegistration<?> registration = discoveryService.get(thingHandler.getThing().getUID());
|
|
||||||
if (registration != null) {
|
|
||||||
// Unregister the discovery service.
|
|
||||||
WWNDiscoveryService service = (WWNDiscoveryService) bundleContext
|
|
||||||
.getService(registration.getReference());
|
|
||||||
service.deactivate();
|
|
||||||
registration.unregister();
|
|
||||||
discoveryService.remove(thingHandler.getThing().getUID());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.removeHandler(thingHandler);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.nest.internal.wwn.test;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
|
||||||
import org.eclipse.jetty.servlet.ServletHandler;
|
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embedded jetty server used in the tests.
|
|
||||||
*
|
|
||||||
* Based on {@code TestServer} of the FS Internet Radio Binding.
|
|
||||||
*
|
|
||||||
* @author Velin Yordanov - Initial contribution
|
|
||||||
* @author Wouter Born - Increase test coverage
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class WWNTestServer {
|
|
||||||
private @Nullable Server server;
|
|
||||||
private String host;
|
|
||||||
private int port;
|
|
||||||
private int timeout;
|
|
||||||
private ServletHolder servletHolder;
|
|
||||||
|
|
||||||
public WWNTestServer(String host, int port, int timeout, ServletHolder servletHolder) {
|
|
||||||
this.host = host;
|
|
||||||
this.port = port;
|
|
||||||
this.timeout = timeout;
|
|
||||||
this.servletHolder = servletHolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startServer() throws Exception {
|
|
||||||
Server server = new Server();
|
|
||||||
|
|
||||||
ServletHandler handler = new ServletHandler();
|
|
||||||
handler.addServletWithMapping(servletHolder, "/*");
|
|
||||||
server.setHandler(handler);
|
|
||||||
|
|
||||||
// HTTP connector
|
|
||||||
ServerConnector http = new ServerConnector(server);
|
|
||||||
http.setHost(host);
|
|
||||||
http.setPort(port);
|
|
||||||
http.setIdleTimeout(timeout);
|
|
||||||
server.addConnector(http);
|
|
||||||
|
|
||||||
server.start();
|
|
||||||
|
|
||||||
this.server = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopServer() throws Exception {
|
|
||||||
Server server = this.server;
|
|
||||||
if (server == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
server.stop();
|
|
||||||
this.server = null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<thing:thing-descriptions bindingId="nest"
|
|
||||||
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">
|
|
||||||
|
|
||||||
<bridge-type id="test_account">
|
|
||||||
<label>Test Account</label>
|
|
||||||
<description>An account for testing the Nest binding</description>
|
|
||||||
<config-description-ref uri="thing-type:nest:account"/>
|
|
||||||
</bridge-type>
|
|
||||||
</thing:thing-descriptions>
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"access_token": "access_token",
|
|
||||||
"expires_in": 315360000
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
{
|
|
||||||
"app_url": "https://camera_app_url",
|
|
||||||
"device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
|
|
||||||
"is_audio_input_enabled": true,
|
|
||||||
"is_online": true,
|
|
||||||
"is_public_share_enabled": false,
|
|
||||||
"is_streaming": false,
|
|
||||||
"is_video_history_enabled": false,
|
|
||||||
"last_event": {
|
|
||||||
"activity_zone_ids": [
|
|
||||||
"id1",
|
|
||||||
"id2"
|
|
||||||
],
|
|
||||||
"animated_image_url": "https://last_event_animated_image_url",
|
|
||||||
"app_url": "https://last_event_app_url",
|
|
||||||
"end_time": "2017-01-22T07:40:38.680Z",
|
|
||||||
"has_motion": true,
|
|
||||||
"has_person": false,
|
|
||||||
"has_sound": false,
|
|
||||||
"image_url": "https://last_event_image_url",
|
|
||||||
"start_time": "2017-01-22T07:40:19.020Z",
|
|
||||||
"urls_expire_time": "2017-02-05T07:40:19.020Z",
|
|
||||||
"web_url": "https://last_event_web_url"
|
|
||||||
},
|
|
||||||
"last_is_online_change": "2017-01-22T08:19:20.000Z",
|
|
||||||
"name": "Upstairs",
|
|
||||||
"name_long": "Upstairs Camera",
|
|
||||||
"snapshot_url": "https://camera_snapshot_url",
|
|
||||||
"software_version": "205-600052",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"web_url": "https://camera_web_url",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"error": "blocked",
|
|
||||||
"type": "https://developer.nest.com/documentation/cloud/error-messages#blocked",
|
|
||||||
"message": "blocked",
|
|
||||||
"instance": "bb514046-edc9-4bca-8239-f7a3cfb0925a"
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"battery_health": "ok",
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV",
|
|
||||||
"is_manual_test_active": false,
|
|
||||||
"is_online": true,
|
|
||||||
"last_connection": "2017-02-02T20:53:05.338Z",
|
|
||||||
"locale": "en-US",
|
|
||||||
"name": "Downstairs",
|
|
||||||
"name_long": "Downstairs Nest Protect",
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"software_version": "3.1rc9",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"ui_color_state": "green",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
|
|
||||||
"where_name": "Downstairs"
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
{
|
|
||||||
"smoke_co_alarms": [
|
|
||||||
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
|
|
||||||
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
|
|
||||||
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
|
|
||||||
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
|
|
||||||
],
|
|
||||||
"name": "Home",
|
|
||||||
"country_code": "US",
|
|
||||||
"postal_code": "98056",
|
|
||||||
"time_zone": "America/Los_Angeles",
|
|
||||||
"away": "home",
|
|
||||||
"thermostats": [
|
|
||||||
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
|
|
||||||
],
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"rhr_enrollment": false,
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"eta_begin": "2017-02-02T03:10:08.000Z",
|
|
||||||
"wwn_security_state": "ok",
|
|
||||||
"wheres": {
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg",
|
|
||||||
"name": "Basement"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ",
|
|
||||||
"name": "Bedroom"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw",
|
|
||||||
"name": "Den"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g",
|
|
||||||
"name": "Dining Room"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
|
|
||||||
"name": "Downstairs"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg",
|
|
||||||
"name": "Entryway"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA",
|
|
||||||
"name": "Family Room"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw",
|
|
||||||
"name": "Hallway"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA",
|
|
||||||
"name": "Kids Room"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA",
|
|
||||||
"name": "Kitchen"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
|
|
||||||
"name": "Living Room"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw",
|
|
||||||
"name": "Master Bedroom"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q",
|
|
||||||
"name": "Office"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA",
|
|
||||||
"name": "Upstairs"
|
|
||||||
},
|
|
||||||
"6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ": {
|
|
||||||
"where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ",
|
|
||||||
"name": "Downstairs Kitchen"
|
|
||||||
},
|
|
||||||
"qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ": {
|
|
||||||
"where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ",
|
|
||||||
"name": "Garage"
|
|
||||||
},
|
|
||||||
"8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ": {
|
|
||||||
"where_id": "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ",
|
|
||||||
"name": "Frog"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA",
|
|
||||||
"name": "Backyard"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA",
|
|
||||||
"name": "Driveway"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g",
|
|
||||||
"name": "Front Yard"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ": {
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ",
|
|
||||||
"name": "Outside"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cameras": [
|
|
||||||
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
|
|
||||||
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
{
|
|
||||||
"ambient_temperature_c": 19.0,
|
|
||||||
"ambient_temperature_f": 66,
|
|
||||||
"away_temperature_high_c": 24.0,
|
|
||||||
"away_temperature_high_f": 76,
|
|
||||||
"away_temperature_low_c": 12.5,
|
|
||||||
"away_temperature_low_f": 55,
|
|
||||||
"can_cool": false,
|
|
||||||
"can_heat": true,
|
|
||||||
"device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV",
|
|
||||||
"eco_temperature_high_c": 24.0,
|
|
||||||
"eco_temperature_high_f": 76,
|
|
||||||
"eco_temperature_low_c": 12.5,
|
|
||||||
"eco_temperature_low_f": 55,
|
|
||||||
"fan_timer_active": false,
|
|
||||||
"fan_timer_duration": 15,
|
|
||||||
"fan_timer_timeout": "1970-01-01T00:00:00.000Z",
|
|
||||||
"has_fan": true,
|
|
||||||
"has_leaf": true,
|
|
||||||
"humidity": 25,
|
|
||||||
"hvac_mode": "heat",
|
|
||||||
"hvac_state": "off",
|
|
||||||
"is_locked": false,
|
|
||||||
"is_online": true,
|
|
||||||
"is_using_emergency_heat": false,
|
|
||||||
"label": "Living Room",
|
|
||||||
"last_connection": "2017-02-02T21:00:06.000Z",
|
|
||||||
"locale": "en-GB",
|
|
||||||
"locked_temp_max_c": 22.0,
|
|
||||||
"locked_temp_max_f": 72,
|
|
||||||
"locked_temp_min_c": 20.0,
|
|
||||||
"locked_temp_min_f": 68,
|
|
||||||
"name": "Living Room (Living Room)",
|
|
||||||
"name_long": "Living Room Thermostat (Living Room)",
|
|
||||||
"previous_hvac_mode": "",
|
|
||||||
"software_version": "5.6-7",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"sunlight_correction_active": false,
|
|
||||||
"sunlight_correction_enabled": true,
|
|
||||||
"target_temperature_c": 15.5,
|
|
||||||
"target_temperature_f": 60,
|
|
||||||
"target_temperature_high_c": 24.0,
|
|
||||||
"target_temperature_high_f": 75,
|
|
||||||
"target_temperature_low_c": 20.0,
|
|
||||||
"target_temperature_low_f": 68,
|
|
||||||
"temperature_scale": "C",
|
|
||||||
"time_to_target": "~0",
|
|
||||||
"time_to_target_training": "ready",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
|
|
||||||
"where_name": "Living Room"
|
|
||||||
}
|
|
|
@ -1,307 +0,0 @@
|
||||||
{
|
|
||||||
"devices": {
|
|
||||||
"cameras": {
|
|
||||||
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ": {
|
|
||||||
"app_url": "https://camera_app_url",
|
|
||||||
"device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
|
|
||||||
"is_audio_input_enabled": true,
|
|
||||||
"is_online": false,
|
|
||||||
"is_public_share_enabled": false,
|
|
||||||
"is_streaming": false,
|
|
||||||
"is_video_history_enabled": false,
|
|
||||||
"last_event": {
|
|
||||||
"activity_zone_ids": [
|
|
||||||
"id1",
|
|
||||||
"id2"
|
|
||||||
],
|
|
||||||
"animated_image_url": "https://last_event_animated_image_url",
|
|
||||||
"app_url": "https://last_event_app_url",
|
|
||||||
"end_time": "2017-01-22T07:40:38.680Z",
|
|
||||||
"has_motion": true,
|
|
||||||
"has_person": false,
|
|
||||||
"has_sound": false,
|
|
||||||
"image_url": "https://last_event_image_url",
|
|
||||||
"start_time": "2017-01-22T07:40:19.020Z",
|
|
||||||
"urls_expire_time": "2017-02-05T07:40:19.020Z",
|
|
||||||
"web_url": "https://last_event_web_url"
|
|
||||||
},
|
|
||||||
"last_is_online_change": "2017-01-22T08:19:20.000Z",
|
|
||||||
"name": "Upstairs",
|
|
||||||
"name_long": "Upstairs Camera",
|
|
||||||
"snapshot_url": "https://camera_snapshot_url",
|
|
||||||
"software_version": "205-600052",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"web_url": "https://camera_web_url",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
|
|
||||||
},
|
|
||||||
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ": {
|
|
||||||
"app_url": "nestmobile://cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
|
|
||||||
"device_id": "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ",
|
|
||||||
"is_audio_input_enabled": true,
|
|
||||||
"is_online": false,
|
|
||||||
"is_public_share_enabled": false,
|
|
||||||
"is_streaming": false,
|
|
||||||
"is_video_history_enabled": false,
|
|
||||||
"last_event": {
|
|
||||||
"end_time": "2016-11-20T07:02:46.860Z",
|
|
||||||
"has_motion": true,
|
|
||||||
"has_person": false,
|
|
||||||
"has_sound": false,
|
|
||||||
"start_time": "2016-11-20T07:02:27.260Z"
|
|
||||||
},
|
|
||||||
"last_is_online_change": "2016-11-20T07:03:42.000Z",
|
|
||||||
"name": "Garage",
|
|
||||||
"name_long": "Garage Camera",
|
|
||||||
"snapshot_url": "https://www.dropcam.com/api/wwn.get_snapshot/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
|
|
||||||
"software_version": "205-600052",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"web_url": "https://home.nest.com/cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
|
|
||||||
"where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"smoke_co_alarms": {
|
|
||||||
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV": {
|
|
||||||
"battery_health": "ok",
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV",
|
|
||||||
"is_manual_test_active": false,
|
|
||||||
"is_online": true,
|
|
||||||
"last_connection": "2017-02-02T20:53:05.338Z",
|
|
||||||
"locale": "en-US",
|
|
||||||
"name": "Downstairs",
|
|
||||||
"name_long": "Downstairs Nest Protect",
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"software_version": "3.1rc9",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"ui_color_state": "green",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
|
|
||||||
"where_name": "Downstairs"
|
|
||||||
},
|
|
||||||
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV": {
|
|
||||||
"battery_health": "ok",
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"device_id": "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
|
|
||||||
"is_manual_test_active": false,
|
|
||||||
"is_online": true,
|
|
||||||
"last_connection": "2017-02-02T20:35:50.051Z",
|
|
||||||
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
|
|
||||||
"locale": "en-US",
|
|
||||||
"name": "Upstairs",
|
|
||||||
"name_long": "Upstairs Nest Protect",
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"software_version": "3.1rc9",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"ui_color_state": "green",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA",
|
|
||||||
"where_name": "Upstairs"
|
|
||||||
},
|
|
||||||
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV": {
|
|
||||||
"battery_health": "ok",
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"device_id": "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
|
|
||||||
"is_manual_test_active": false,
|
|
||||||
"is_online": true,
|
|
||||||
"last_connection": "2017-02-02T11:04:18.804Z",
|
|
||||||
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
|
|
||||||
"locale": "en-US",
|
|
||||||
"name": "Downstairs Kitchen",
|
|
||||||
"name_long": "Downstairs Kitchen Nest Protect",
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"software_version": "3.1rc9",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"ui_color_state": "green",
|
|
||||||
"where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ",
|
|
||||||
"where_name": "Downstairs Kitchen"
|
|
||||||
},
|
|
||||||
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV": {
|
|
||||||
"battery_health": "ok",
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"device_id": "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
|
|
||||||
"is_manual_test_active": false,
|
|
||||||
"is_online": true,
|
|
||||||
"last_connection": "2017-02-02T13:30:34.187Z",
|
|
||||||
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
|
|
||||||
"locale": "en-US",
|
|
||||||
"name": "Living Room",
|
|
||||||
"name_long": "Living Room Nest Protect",
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"software_version": "3.1rc9",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"ui_color_state": "green",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
|
|
||||||
"where_name": "Living Room"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"thermostats": {
|
|
||||||
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV": {
|
|
||||||
"ambient_temperature_c": 19.0,
|
|
||||||
"ambient_temperature_f": 66,
|
|
||||||
"away_temperature_high_c": 24.0,
|
|
||||||
"away_temperature_high_f": 76,
|
|
||||||
"away_temperature_low_c": 12.5,
|
|
||||||
"away_temperature_low_f": 55,
|
|
||||||
"can_cool": false,
|
|
||||||
"can_heat": true,
|
|
||||||
"device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV",
|
|
||||||
"eco_temperature_high_c": 24.0,
|
|
||||||
"eco_temperature_high_f": 76,
|
|
||||||
"eco_temperature_low_c": 12.5,
|
|
||||||
"eco_temperature_low_f": 55,
|
|
||||||
"fan_timer_active": false,
|
|
||||||
"fan_timer_duration": 15,
|
|
||||||
"fan_timer_timeout": "1970-01-01T00:00:00.000Z",
|
|
||||||
"has_fan": true,
|
|
||||||
"has_leaf": true,
|
|
||||||
"humidity": 25,
|
|
||||||
"hvac_mode": "heat",
|
|
||||||
"hvac_state": "off",
|
|
||||||
"is_locked": false,
|
|
||||||
"is_online": true,
|
|
||||||
"is_using_emergency_heat": false,
|
|
||||||
"label": "Living Room",
|
|
||||||
"last_connection": "2017-02-02T21:00:06.000Z",
|
|
||||||
"locale": "en-GB",
|
|
||||||
"locked_temp_max_c": 22.0,
|
|
||||||
"locked_temp_max_f": 72,
|
|
||||||
"locked_temp_min_c": 20.0,
|
|
||||||
"locked_temp_min_f": 68,
|
|
||||||
"name": "Living Room (Living Room)",
|
|
||||||
"name_long": "Living Room Thermostat (Living Room)",
|
|
||||||
"previous_hvac_mode": "",
|
|
||||||
"software_version": "5.6-7",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"sunlight_correction_active": false,
|
|
||||||
"sunlight_correction_enabled": true,
|
|
||||||
"target_temperature_c": 15.5,
|
|
||||||
"target_temperature_f": 60,
|
|
||||||
"target_temperature_high_c": 24.0,
|
|
||||||
"target_temperature_high_f": 75,
|
|
||||||
"target_temperature_low_c": 20.0,
|
|
||||||
"target_temperature_low_f": 68,
|
|
||||||
"temperature_scale": "C",
|
|
||||||
"time_to_target": "~0",
|
|
||||||
"time_to_target_training": "ready",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
|
|
||||||
"where_name": "Living Room"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"access_token": "c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
|
|
||||||
"client_version": 1
|
|
||||||
},
|
|
||||||
"structures": {
|
|
||||||
"ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A": {
|
|
||||||
"away": "home",
|
|
||||||
"cameras": [
|
|
||||||
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
|
|
||||||
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
|
|
||||||
],
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"country_code": "US",
|
|
||||||
"eta_begin": "2017-02-02T03:10:08.000Z",
|
|
||||||
"name": "Home",
|
|
||||||
"postal_code": "98056",
|
|
||||||
"rhr_enrollment": false,
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"smoke_co_alarms": [
|
|
||||||
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
|
|
||||||
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
|
|
||||||
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
|
|
||||||
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
|
|
||||||
],
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"thermostats": [
|
|
||||||
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
|
|
||||||
],
|
|
||||||
"time_zone": "America/Los_Angeles",
|
|
||||||
"wheres": {
|
|
||||||
"6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ": {
|
|
||||||
"name": "Downstairs Kitchen",
|
|
||||||
"where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ"
|
|
||||||
},
|
|
||||||
"8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ": {
|
|
||||||
"name": "Frog",
|
|
||||||
"where_id": "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ"
|
|
||||||
},
|
|
||||||
"qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ": {
|
|
||||||
"name": "Garage",
|
|
||||||
"where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA": {
|
|
||||||
"name": "Family Room",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA": {
|
|
||||||
"name": "Kitchen",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw": {
|
|
||||||
"name": "Hallway",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg": {
|
|
||||||
"name": "Basement",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA": {
|
|
||||||
"name": "Kids Room",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw": {
|
|
||||||
"name": "Master Bedroom",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg": {
|
|
||||||
"name": "Downstairs",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA": {
|
|
||||||
"name": "Driveway",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw": {
|
|
||||||
"name": "Den",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ": {
|
|
||||||
"name": "Bedroom",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg": {
|
|
||||||
"name": "Entryway",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA": {
|
|
||||||
"name": "Upstairs",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw": {
|
|
||||||
"name": "Living Room",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ": {
|
|
||||||
"name": "Outside",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g": {
|
|
||||||
"name": "Dining Room",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA": {
|
|
||||||
"name": "Backyard",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q": {
|
|
||||||
"name": "Office",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g": {
|
|
||||||
"name": "Front Yard",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"wwn_security_state": "ok"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"path": "/",
|
|
||||||
"data": {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
{
|
|
||||||
"path": "/",
|
|
||||||
"data": {
|
|
||||||
"devices": {
|
|
||||||
"cameras": {
|
|
||||||
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ": {
|
|
||||||
"device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ"
|
|
||||||
},
|
|
||||||
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ": {
|
|
||||||
"device_id": "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"smoke_co_alarms": {
|
|
||||||
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV": {
|
|
||||||
"device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
|
|
||||||
},
|
|
||||||
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV": {
|
|
||||||
"device_id": "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV"
|
|
||||||
},
|
|
||||||
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV": {
|
|
||||||
"device_id": "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV"
|
|
||||||
},
|
|
||||||
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV": {
|
|
||||||
"device_id": "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"thermostats": {
|
|
||||||
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV": {
|
|
||||||
"device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
|
|
||||||
},
|
|
||||||
"OTQoylk2h5Ld3cfpm3esR0qx-iQr8PMV": {
|
|
||||||
"device_id": "OTQoylk2h5Ld3cfpm3esR0qx-iQr8PMV"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"structures": {
|
|
||||||
"ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A": {
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A"
|
|
||||||
},
|
|
||||||
"SylKI7puaWd56ILAcJ46LzmtdZc3L4wGzScs8yLc5zccJofBIW9KTJ": {
|
|
||||||
"structure_id": "SylKI7puaWd56ILAcJ46LzmtdZc3L4wGzScs8yLc5zccJofBIW9KTJ"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,314 +0,0 @@
|
||||||
{
|
|
||||||
"path": "/",
|
|
||||||
"data": {
|
|
||||||
"devices": {
|
|
||||||
"cameras": {
|
|
||||||
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ": {
|
|
||||||
"app_url": "https://camera_app_url",
|
|
||||||
"device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
|
|
||||||
"is_audio_input_enabled": true,
|
|
||||||
"is_online": true,
|
|
||||||
"is_public_share_enabled": false,
|
|
||||||
"is_streaming": false,
|
|
||||||
"is_video_history_enabled": false,
|
|
||||||
"last_event": {
|
|
||||||
"activity_zone_ids": [
|
|
||||||
"id1",
|
|
||||||
"id2"
|
|
||||||
],
|
|
||||||
"animated_image_url": "https://last_event_animated_image_url",
|
|
||||||
"app_url": "https://last_event_app_url",
|
|
||||||
"end_time": "2017-01-22T07:40:38.680Z",
|
|
||||||
"has_motion": true,
|
|
||||||
"has_person": false,
|
|
||||||
"has_sound": false,
|
|
||||||
"image_url": "https://last_event_image_url",
|
|
||||||
"start_time": "2017-01-22T07:40:19.020Z",
|
|
||||||
"urls_expire_time": "2017-02-05T07:40:19.020Z",
|
|
||||||
"web_url": "https://last_event_web_url"
|
|
||||||
},
|
|
||||||
"last_is_online_change": "2017-01-22T08:19:20.000Z",
|
|
||||||
"name": "Upstairs",
|
|
||||||
"name_long": "Upstairs Camera",
|
|
||||||
"public_share_url": "https://camera_public_share_url",
|
|
||||||
"snapshot_url": "https://camera_snapshot_url",
|
|
||||||
"software_version": "205-600052",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"web_url": "https://camera_web_url",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
|
|
||||||
},
|
|
||||||
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ": {
|
|
||||||
"app_url": "nestmobile://cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
|
|
||||||
"device_id": "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ",
|
|
||||||
"is_audio_input_enabled": true,
|
|
||||||
"is_online": false,
|
|
||||||
"is_public_share_enabled": false,
|
|
||||||
"is_streaming": false,
|
|
||||||
"is_video_history_enabled": false,
|
|
||||||
"last_event": {
|
|
||||||
"end_time": "2016-11-20T07:02:46.860Z",
|
|
||||||
"has_motion": true,
|
|
||||||
"has_person": false,
|
|
||||||
"has_sound": false,
|
|
||||||
"start_time": "2016-11-20T07:02:27.260Z"
|
|
||||||
},
|
|
||||||
"last_is_online_change": "2016-11-20T07:03:42.000Z",
|
|
||||||
"name": "Garage",
|
|
||||||
"name_long": "Garage Camera",
|
|
||||||
"snapshot_url": "https://www.dropcam.com/api/wwn.get_snapshot/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
|
|
||||||
"software_version": "205-600052",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"web_url": "https://home.nest.com/cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
|
|
||||||
"where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"smoke_co_alarms": {
|
|
||||||
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV": {
|
|
||||||
"battery_health": "ok",
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV",
|
|
||||||
"is_manual_test_active": false,
|
|
||||||
"is_online": true,
|
|
||||||
"last_connection": "2017-02-02T20:53:05.338Z",
|
|
||||||
"last_manual_test_time": "2016-10-31T23:59:59.000Z",
|
|
||||||
"locale": "en-US",
|
|
||||||
"name": "Downstairs",
|
|
||||||
"name_long": "Downstairs Nest Protect",
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"software_version": "3.1rc9",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"ui_color_state": "green",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
|
|
||||||
"where_name": "Downstairs"
|
|
||||||
},
|
|
||||||
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV": {
|
|
||||||
"battery_health": "ok",
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"device_id": "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
|
|
||||||
"is_manual_test_active": false,
|
|
||||||
"is_online": true,
|
|
||||||
"last_connection": "2017-02-02T20:35:50.051Z",
|
|
||||||
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
|
|
||||||
"locale": "en-US",
|
|
||||||
"name": "Upstairs",
|
|
||||||
"name_long": "Upstairs Nest Protect",
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"software_version": "3.1rc9",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"ui_color_state": "green",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA",
|
|
||||||
"where_name": "Upstairs"
|
|
||||||
},
|
|
||||||
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV": {
|
|
||||||
"battery_health": "ok",
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"device_id": "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
|
|
||||||
"is_manual_test_active": false,
|
|
||||||
"is_online": true,
|
|
||||||
"last_connection": "2017-02-02T11:04:18.804Z",
|
|
||||||
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
|
|
||||||
"locale": "en-US",
|
|
||||||
"name": "Downstairs Kitchen",
|
|
||||||
"name_long": "Downstairs Kitchen Nest Protect",
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"software_version": "3.1rc9",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"ui_color_state": "green",
|
|
||||||
"where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ",
|
|
||||||
"where_name": "Downstairs Kitchen"
|
|
||||||
},
|
|
||||||
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV": {
|
|
||||||
"battery_health": "ok",
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"device_id": "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
|
|
||||||
"is_manual_test_active": false,
|
|
||||||
"is_online": true,
|
|
||||||
"last_connection": "2017-02-02T13:30:34.187Z",
|
|
||||||
"last_manual_test_time": "1970-01-01T00:00:00.000Z",
|
|
||||||
"locale": "en-US",
|
|
||||||
"name": "Living Room",
|
|
||||||
"name_long": "Living Room Nest Protect",
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"software_version": "3.1rc9",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"ui_color_state": "green",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
|
|
||||||
"where_name": "Living Room"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"thermostats": {
|
|
||||||
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV": {
|
|
||||||
"ambient_temperature_c": 19.0,
|
|
||||||
"ambient_temperature_f": 66,
|
|
||||||
"away_temperature_high_c": 24.0,
|
|
||||||
"away_temperature_high_f": 76,
|
|
||||||
"away_temperature_low_c": 12.5,
|
|
||||||
"away_temperature_low_f": 55,
|
|
||||||
"can_cool": false,
|
|
||||||
"can_heat": true,
|
|
||||||
"device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV",
|
|
||||||
"eco_temperature_high_c": 24.0,
|
|
||||||
"eco_temperature_high_f": 76,
|
|
||||||
"eco_temperature_low_c": 12.5,
|
|
||||||
"eco_temperature_low_f": 55,
|
|
||||||
"fan_timer_active": false,
|
|
||||||
"fan_timer_duration": 15,
|
|
||||||
"fan_timer_timeout": "1970-01-01T00:00:00.000Z",
|
|
||||||
"has_fan": true,
|
|
||||||
"has_leaf": true,
|
|
||||||
"humidity": 25,
|
|
||||||
"hvac_mode": "heat",
|
|
||||||
"hvac_state": "off",
|
|
||||||
"is_locked": false,
|
|
||||||
"is_online": true,
|
|
||||||
"is_using_emergency_heat": false,
|
|
||||||
"label": "Living Room",
|
|
||||||
"last_connection": "2017-02-02T21:00:06.000Z",
|
|
||||||
"locale": "en-GB",
|
|
||||||
"locked_temp_max_c": 22.0,
|
|
||||||
"locked_temp_max_f": 72,
|
|
||||||
"locked_temp_min_c": 20.0,
|
|
||||||
"locked_temp_min_f": 68,
|
|
||||||
"name": "Living Room (Living Room)",
|
|
||||||
"name_long": "Living Room Thermostat (Living Room)",
|
|
||||||
"previous_hvac_mode": "",
|
|
||||||
"software_version": "5.6-7",
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"sunlight_correction_active": false,
|
|
||||||
"sunlight_correction_enabled": true,
|
|
||||||
"target_temperature_c": 15.5,
|
|
||||||
"target_temperature_f": 60,
|
|
||||||
"target_temperature_high_c": 24.0,
|
|
||||||
"target_temperature_high_f": 75,
|
|
||||||
"target_temperature_low_c": 20.0,
|
|
||||||
"target_temperature_low_f": 68,
|
|
||||||
"temperature_scale": "C",
|
|
||||||
"time_to_target": "~0",
|
|
||||||
"time_to_target_training": "ready",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
|
|
||||||
"where_name": "Living Room"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"access_token": "c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
|
|
||||||
"client_version": 1
|
|
||||||
},
|
|
||||||
"structures": {
|
|
||||||
"ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A": {
|
|
||||||
"away": "home",
|
|
||||||
"cameras": [
|
|
||||||
"_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
|
|
||||||
"VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
|
|
||||||
],
|
|
||||||
"co_alarm_state": "ok",
|
|
||||||
"country_code": "US",
|
|
||||||
"eta_begin": "2017-02-02T03:10:08.000Z",
|
|
||||||
"name": "Home",
|
|
||||||
"peak_period_end_time": "2017-07-01T01:03:08.400Z",
|
|
||||||
"peak_period_start_time": "2017-06-01T13:31:10.870Z",
|
|
||||||
"postal_code": "98056",
|
|
||||||
"rhr_enrollment": false,
|
|
||||||
"smoke_alarm_state": "ok",
|
|
||||||
"smoke_co_alarms": [
|
|
||||||
"p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
|
|
||||||
"p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
|
|
||||||
"p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
|
|
||||||
"p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
|
|
||||||
],
|
|
||||||
"structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
|
|
||||||
"thermostats": [
|
|
||||||
"G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
|
|
||||||
],
|
|
||||||
"time_zone": "America/Los_Angeles",
|
|
||||||
"wheres": {
|
|
||||||
"6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ": {
|
|
||||||
"name": "Downstairs Kitchen",
|
|
||||||
"where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ"
|
|
||||||
},
|
|
||||||
"8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ": {
|
|
||||||
"name": "Frog",
|
|
||||||
"where_id": "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ"
|
|
||||||
},
|
|
||||||
"qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ": {
|
|
||||||
"name": "Garage",
|
|
||||||
"where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA": {
|
|
||||||
"name": "Family Room",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA": {
|
|
||||||
"name": "Kitchen",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw": {
|
|
||||||
"name": "Hallway",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg": {
|
|
||||||
"name": "Basement",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA": {
|
|
||||||
"name": "Kids Room",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw": {
|
|
||||||
"name": "Master Bedroom",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg": {
|
|
||||||
"name": "Downstairs",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA": {
|
|
||||||
"name": "Driveway",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw": {
|
|
||||||
"name": "Den",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ": {
|
|
||||||
"name": "Bedroom",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg": {
|
|
||||||
"name": "Entryway",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA": {
|
|
||||||
"name": "Upstairs",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw": {
|
|
||||||
"name": "Living Room",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ": {
|
|
||||||
"name": "Outside",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g": {
|
|
||||||
"name": "Dining Room",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA": {
|
|
||||||
"name": "Backyard",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q": {
|
|
||||||
"name": "Office",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q"
|
|
||||||
},
|
|
||||||
"z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g": {
|
|
||||||
"name": "Front Yard",
|
|
||||||
"where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"wwn_security_state": "ok"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,7 +29,6 @@
|
||||||
<module>org.openhab.binding.mqtt.homeassistant.tests</module>
|
<module>org.openhab.binding.mqtt.homeassistant.tests</module>
|
||||||
<module>org.openhab.binding.mqtt.homie.tests</module>
|
<module>org.openhab.binding.mqtt.homie.tests</module>
|
||||||
<module>org.openhab.binding.mqtt.ruuvigateway.tests</module>
|
<module>org.openhab.binding.mqtt.ruuvigateway.tests</module>
|
||||||
<module>org.openhab.binding.nest.tests</module>
|
|
||||||
<module>org.openhab.binding.ntp.tests</module>
|
<module>org.openhab.binding.ntp.tests</module>
|
||||||
<module>org.openhab.binding.systeminfo.tests</module>
|
<module>org.openhab.binding.systeminfo.tests</module>
|
||||||
<module>org.openhab.binding.tradfri.tests</module>
|
<module>org.openhab.binding.tradfri.tests</module>
|
||||||
|
|
Loading…
Reference in New Issue