[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:
@@ -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>
|
||||
Reference in New Issue
Block a user