[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.homie.tests/ @davidgraeff
|
||||
/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.systeminfo.tests/ @svilenvul
|
||||
/itests/org.openhab.binding.tradfri.tests/ @cweitkamp @kaikreuzer
|
||||
|
|
|
@ -1,51 +1,41 @@
|
|||
# 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.
|
||||
|
||||
It is also possible to use the older WWN API with this binding.
|
||||
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.
|
||||
Because the SDM API runs 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.
|
||||
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.
|
||||
|
||||
> 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
|
||||
|
||||
The table below lists the Nest binding thing types:
|
||||
|
||||
| Things | Description | SDM Thing Type | WWN Thing Type |
|
||||
|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|----------------|--------------------|
|
||||
| Nest Account (SDM, WWN) | An account for using the Nest (SDM/WWN) REST API | sdm_account | wwn_account |
|
||||
| Nest Cam (Indoor, IQ, Outdoor), Dropcam | A Nest Cam registered with your account | sdm_camera | wwn_camera |
|
||||
| Nest Hello Doorbell | A Nest Doorbell registered with your account | sdm_doorbell | wwn_camera |
|
||||
| Nest Hub (Max) | A Nest Display registered with your account | sdm_display | wwn_camera |
|
||||
| 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 | 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 |
|
||||
| Things | Description | Thing Type |
|
||||
|-----------------------------------------|------------------------------------------------------------------------|----------------|
|
||||
| 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 |
|
||||
| Nest Hello Doorbell | A Nest Doorbell registered with your account | sdm_doorbell |
|
||||
| Nest Hub (Max) | A Nest Display registered with your account | sdm_display |
|
||||
| Nest Thermostat (E) | A Thermostat to control the various aspects of the house's HVAC system | sdm_thermostat |
|
||||
|
||||
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.
|
||||
|
||||
## SDM Account Configuration
|
||||
## Account Configuration
|
||||
|
||||
### Google Account Requirement
|
||||
|
||||
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)
|
||||
|
||||
### 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.
|
||||
|
||||
|
@ -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.
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
### SDM/WWN Account Channels
|
||||
### Account 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.
|
||||
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.
|
||||
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 |
|
||||
|---------------------|----------------------|------------------------------------------------------------------------|:----------:|
|
||||
|
@ -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).
|
||||
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
|
||||
|
||||
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_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_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.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
|
||||
|
||||
|
@ -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.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.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
|
||||
|
||||
|
@ -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.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.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
|
||||
|
||||
|
@ -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.SDMTemperatureHeat.label = Heat Temperature
|
||||
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
|
||||
|
||||
|
|
|
@ -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.homie.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.systeminfo.tests</module>
|
||||
<module>org.openhab.binding.tradfri.tests</module>
|
||||
|
|
Loading…
Reference in New Issue