/itests/org.openhab.binding.mqtt.homeassistant.tests/ @davidgraeff
/itests/org.openhab.binding.mqtt.homie.tests/ @davidgraeff
/itests/org.openhab.binding.mqtt.ruuvigateway.tests/ @ssalonen
-/itests/org.openhab.binding.nest.tests/ @wborn
/itests/org.openhab.binding.ntp.tests/ @marcelrv
/itests/org.openhab.binding.systeminfo.tests/ @svilenvul
/itests/org.openhab.binding.tradfri.tests/ @cweitkamp @kaikreuzer
# 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.
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`.
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 |
|---------------------|----------------------|------------------------------------------------------------------------|:----------:|
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.
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.
+++ /dev/null
-/**
- * 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";
-}
+++ /dev/null
-/**
- * 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;
- }
-}
+++ /dev/null
-/**
- * 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);
- }
-}
+++ /dev/null
-/**
- * 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;
-}
+++ /dev/null
-/**
- * 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 = "";
-}
+++ /dev/null
-/**
- * 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 = "";
-}
+++ /dev/null
-/**
- * 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() //
- );
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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);
- }
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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);
- }
-}
+++ /dev/null
-/**
- * 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);
- }
-}
+++ /dev/null
-/**
- * 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);
- }
-}
+++ /dev/null
-/**
- * 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);
- }
-}
+++ /dev/null
-/**
- * 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();
- }
- }
- }
-}
+++ /dev/null
-/**
- * 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);
-}
+++ /dev/null
-/**
- * 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);
- }
- }
-}
+++ /dev/null
-/**
- * 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;
- }
-}
+++ /dev/null
-/**
- * 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);
- }
- }
-}
+++ /dev/null
-/**
- * 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);
- }
- }
-}
+++ /dev/null
-/**
- * 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);
- }
- }
-}
+++ /dev/null
-/**
- * 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);
-}
+++ /dev/null
-/**
- * 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);
-}
+++ /dev/null
-/**
- * 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);
- }
- }
-}
+++ /dev/null
-/**
- * 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");
- }
- }
-}
+++ /dev/null
-/**
- * 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);
- }
-}
+++ /dev/null
-/**
- * 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();
- });
- }
-}
+++ /dev/null
-/**
- * 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));
- }
-}
+++ /dev/null
-<?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>
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
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
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
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
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-This content is produced and maintained by the openHAB project.
-
-* Project home: https://www.openhab.org
-
-== Declared Project Licenses
-
-This program and the accompanying materials are made available under the terms
-of the Eclipse Public License 2.0 which is available at
-https://www.eclipse.org/legal/epl-2.0/.
-
-== Source Code
-
-https://github.com/openhab/openhab-addons
+++ /dev/null
-# Nest Binding Tests
-
-[Nest Labs](https://nest.com/) developed/acquired the Wi-Fi enabled Nest Learning Thermostat, the Nest Protect Smoke+CO detector, and the Nest Cam.
-These devices are supported by this binding, which communicates with the Nest API over a secure, RESTful API to Nest's servers.
-Monitoring ambient temperature and humidity, changing HVAC mode, changing heat or cool setpoints, monitoring and changing your "home/away" status, and monitoring your Nest Protects and Nest Cams can be accomplished through this binding.
-
-This binding is to test and verify the nest binding.
+++ /dev/null
--include: ../itest-common.bndrun
-
-Bundle-SymbolicName: ${project.artifactId}
-Fragment-Host: org.openhab.binding.nest
-
--runrequires: \
- bnd.identity;id='org.openhab.binding.nest.tests'
-
-# We would like to use the "volatile" storage only
--runblacklist: \
- bnd.identity;id='org.openhab.core.storage.json',\
- bnd.identity;id='jakarta.ws.rs-api'
-
-#
-# done
-#
--runbundles: \
- org.eclipse.equinox.event;version='[1.4.300,1.4.301)',\
- org.osgi.service.event;version='[1.4.0,1.4.1)',\
- org.osgi.service.jaxrs;version='[1.0.0,1.0.1)',\
- org.hamcrest;version='[2.2.0,2.2.1)',\
- org.opentest4j;version='[1.2.0,1.2.1)',\
- jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\
- com.sun.xml.bind.jaxb-osgi;version='[2.3.3,2.3.4)',\
- org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\
- org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\
- org.apache.aries.javax.jax.rs-api;version='[1.0.1,1.0.2)',\
- jakarta.annotation-api;version='[1.3.5,1.3.6)',\
- org.apache.aries.component-dsl.component-dsl;version='[1.2.2,1.2.3)',\
- stax2-api;version='[4.2.1,4.2.2)',\
- jakarta.inject.jakarta.inject-api;version='[2.0.0,2.0.1)',\
- org.glassfish.hk2.external.javax.inject;version='[2.4.0,2.4.1)',\
- si-units;version='[2.1.0,2.1.1)',\
- si.uom.si-quantity;version='[2.1.0,2.1.1)',\
- org.apache.aries.jax.rs.whiteboard;version='[2.0.0,2.0.1)',\
- org.osgi.util.function;version='[1.2.0,1.2.1)',\
- org.osgi.util.promise;version='[1.2.0,1.2.1)',\
- ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
- ch.qos.logback.core;version='[1.2.11,1.2.12)',\
- biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
- org.jsr-305;version='[3.0.2,3.0.3)',\
- com.google.gson;version='[2.9.1,2.9.2)',\
- org.objectweb.asm;version='[9.4.0,9.4.1)',\
- com.sun.jna;version='[5.12.1,5.12.2)',\
- org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\
- org.apache.felix.http.servlet-api;version='[1.2.0,1.2.1)',\
- org.apache.felix.scr;version='[2.2.4,2.2.5)',\
- org.eclipse.jetty.client;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.http;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.io;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.jaas;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.security;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.server;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.servlet;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.util;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.util.ajax;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.websocket.api;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.websocket.client;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.websocket.common;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.xml;version='[9.4.50,9.4.51)',\
- org.ops4j.pax.logging.pax-logging-api;version='[2.2.0,2.2.1)',\
- org.ops4j.pax.web.pax-web-api;version='[8.0.15,8.0.16)',\
- org.ops4j.pax.web.pax-web-jetty;version='[8.0.15,8.0.16)',\
- org.ops4j.pax.web.pax-web-runtime;version='[8.0.15,8.0.16)',\
- org.ops4j.pax.web.pax-web-spi;version='[8.0.15,8.0.16)',\
- org.ops4j.pax.web.pax-web-tomcat-common;version='[8.0.15,8.0.16)',\
- org.osgi.service.component;version='[1.5.0,1.5.1)',\
- junit-jupiter-api;version='[5.9.2,5.9.3)',\
- junit-jupiter-engine;version='[5.9.2,5.9.3)',\
- junit-platform-commons;version='[1.9.2,1.9.3)',\
- junit-platform-engine;version='[1.9.2,1.9.3)',\
- junit-platform-launcher;version='[1.9.2,1.9.3)',\
- net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\
- net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
- org.eclipse.jetty.alpn.client;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.http2.client;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.http2.common;version='[9.4.50,9.4.51)',\
- org.eclipse.jetty.http2.hpack;version='[9.4.50,9.4.51)',\
- org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\
- org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
- org.objenesis;version='[3.3.0,3.3.1)',\
- xstream;version='[1.4.20,1.4.21)',\
- org.apache.aries.spifly.dynamic.bundle;version='[1.3.6,1.3.7)',\
- org.objectweb.asm.commons;version='[9.4.0,9.4.1)',\
- org.objectweb.asm.tree;version='[9.4.0,9.4.1)',\
- org.objectweb.asm.tree.analysis;version='[9.4.0,9.4.1)',\
- org.objectweb.asm.util;version='[9.4.0,9.4.1)',\
- org.openhab.binding.nest;version='[4.1.0,4.1.1)',\
- org.openhab.binding.nest.tests;version='[4.1.0,4.1.1)',\
- org.openhab.core;version='[4.1.0,4.1.1)',\
- org.openhab.core.auth.oauth2client;version='[4.1.0,4.1.1)',\
- org.openhab.core.config.core;version='[4.1.0,4.1.1)',\
- org.openhab.core.config.discovery;version='[4.1.0,4.1.1)',\
- org.openhab.core.io.console;version='[4.1.0,4.1.1)',\
- org.openhab.core.io.net;version='[4.1.0,4.1.1)',\
- org.openhab.core.test;version='[4.1.0,4.1.1)',\
- org.openhab.core.thing;version='[4.1.0,4.1.1)',\
- org.openhab.core.transform;version='[4.1.0,4.1.1)',\
- org.openhab.base-fixes;version='[1.0.0,1.0.1)',\
- javax.measure.unit-api;version='[2.2.0,2.2.1)',\
- org.apiguardian.api;version='[1.1.2,1.1.3)',\
- tech.units.indriya;version='[2.2.0,2.2.1)',\
- uom-lib-common;version='[2.2.0,2.2.1)',\
- com.fasterxml.woodstox.woodstox-core;version='[6.5.1,6.5.2)',\
- org.apache.cxf.cxf-core;version='[3.6.1,3.6.2)',\
- org.apache.cxf.cxf-rt-frontend-jaxrs;version='[3.6.1,3.6.2)',\
- org.apache.cxf.cxf-rt-rs-client;version='[3.6.1,3.6.2)',\
- org.apache.cxf.cxf-rt-rs-sse;version='[3.6.1,3.6.2)',\
- org.apache.cxf.cxf-rt-security;version='[3.6.1,3.6.2)',\
- org.apache.cxf.cxf-rt-transports-http;version='[3.6.1,3.6.2)',\
- org.apache.ws.xmlschema.core;version='[2.3.0,2.3.1)',\
- io.methvin.directory-watcher;version='[0.18.0,0.18.1)'
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-
- <modelVersion>4.0.0</modelVersion>
-
- <parent>
- <groupId>org.openhab.addons.itests</groupId>
- <artifactId>org.openhab.addons.reactor.itests</artifactId>
- <version>4.1.0-SNAPSHOT</version>
- </parent>
-
- <artifactId>org.openhab.binding.nest.tests</artifactId>
-
- <name>openHAB Add-ons :: Integration Tests :: Nest Binding Tests</name>
-
- <dependencies>
- <dependency>
- <groupId>org.openhab.addons.bundles</groupId>
- <artifactId>org.openhab.binding.nest</artifactId>
- <version>${project.version}</version>
- </dependency>
- </dependencies>
-
-</project>
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.dto;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
-import java.util.stream.Collectors;
-
-import javax.measure.Unit;
-import javax.measure.quantity.Temperature;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.nest.internal.wwn.WWNUtils;
-import org.openhab.core.library.unit.ImperialUnits;
-import org.openhab.core.library.unit.SIUnits;
-
-/**
- * Utility class for working with Nest test data in unit tests.
- *
- * @author Wouter Born - Initial contribution
- */
-@NonNullByDefault
-public final class WWNDataUtil {
-
- public static final String COMPLETE_DATA_FILE_NAME = "top-level-streaming-data.json";
- public static final String INCOMPLETE_DATA_FILE_NAME = "top-level-streaming-data-incomplete.json";
- public static final String EMPTY_DATA_FILE_NAME = "top-level-streaming-data-empty.json";
-
- public static final String CAMERA1_DEVICE_ID = "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ";
- public static final String CAMERA1_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA";
-
- public static final String CAMERA2_DEVICE_ID = "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ";
- public static final String CAMERA2_WHERE_ID = "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ";
-
- public static final String SMOKE1_DEVICE_ID = "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV";
- public static final String SMOKE1_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg";
-
- public static final String SMOKE2_DEVICE_ID = "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV";
- public static final String SMOKE2_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA";
-
- public static final String SMOKE3_DEVICE_ID = "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV";
- public static final String SMOKE3_WHERE_ID = "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ";
-
- public static final String SMOKE4_DEVICE_ID = "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV";
- public static final String SMOKE4_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw";
-
- public static final String STRUCTURE1_STRUCTURE_ID = "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A";
-
- public static final String THERMOSTAT1_DEVICE_ID = "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV";
- public static final String THERMOSTAT1_WHERE_ID = "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw";
-
- private WWNDataUtil() {
- // Hidden utility class constructor
- }
-
- public static Reader openDataReader(String fileName) {
- String packagePath = (WWNDataUtil.class.getPackage().getName()).replaceAll("\\.", "/");
- String filePath = "/" + packagePath + "/" + fileName;
- InputStream inputStream = WWNDataUtil.class.getClassLoader().getResourceAsStream(filePath);
- return new InputStreamReader(inputStream, StandardCharsets.UTF_8);
- }
-
- public static <T> T fromJson(String fileName, Class<T> dataClass) throws IOException {
- try (Reader reader = openDataReader(fileName)) {
- return WWNUtils.fromJson(reader, dataClass);
- }
- }
-
- public static String fromFile(String fileName, Unit<Temperature> temperatureUnit) throws IOException {
- String json = fromFile(fileName);
- if (SIUnits.CELSIUS.equals(temperatureUnit)) {
- json = json.replace("\"temperature_scale\": \"F\"", "\"temperature_scale\": \"C\"");
- } else if (ImperialUnits.FAHRENHEIT.equals(temperatureUnit)) {
- json = json.replace("\"temperature_scale\": \"C\"", "\"temperature_scale\": \"F\"");
- }
- return json;
- }
-
- public static String fromFile(String fileName) throws IOException {
- try (Reader reader = openDataReader(fileName)) {
- return new BufferedReader(reader).lines().parallel().collect(Collectors.joining("\n"));
- }
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.dto;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
-
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.core.library.unit.SIUnits;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Test cases for gson parsing of model classes
- *
- * @author David Bennett - Initial contribution
- * @author Wouter Born - Increase test coverage
- */
-@NonNullByDefault
-public class WWNGsonParsingTest {
-
- private final Logger logger = LoggerFactory.getLogger(WWNGsonParsingTest.class);
-
- private static void assertEqualDateTime(String expected, Date actual) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
- assertEquals(expected, sdf.format(actual));
- }
-
- @Test
- public void verifyCompleteInput() throws IOException {
- WWNTopLevelData topLevel = fromJson("top-level-data.json", WWNTopLevelData.class);
-
- assertEquals(topLevel.getDevices().getThermostats().size(), 1);
- assertNotNull(topLevel.getDevices().getThermostats().get(THERMOSTAT1_DEVICE_ID));
- assertEquals(topLevel.getDevices().getCameras().size(), 2);
- assertNotNull(topLevel.getDevices().getCameras().get(CAMERA1_DEVICE_ID));
- assertNotNull(topLevel.getDevices().getCameras().get(CAMERA2_DEVICE_ID));
- assertEquals(topLevel.getDevices().getSmokeCoAlarms().size(), 4);
- assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE1_DEVICE_ID));
- assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE2_DEVICE_ID));
- assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE3_DEVICE_ID));
- assertNotNull(topLevel.getDevices().getSmokeCoAlarms().get(SMOKE4_DEVICE_ID));
- }
-
- @Test
- public void verifyCompleteStreamingInput() throws IOException {
- WWNTopLevelStreamingData topLevelStreamingData = fromJson("top-level-streaming-data.json",
- WWNTopLevelStreamingData.class);
-
- assertEquals("/", topLevelStreamingData.getPath());
-
- WWNTopLevelData data = topLevelStreamingData.getData();
- assertEquals(data.getDevices().getThermostats().size(), 1);
- assertNotNull(data.getDevices().getThermostats().get(THERMOSTAT1_DEVICE_ID));
- assertEquals(data.getDevices().getCameras().size(), 2);
- assertNotNull(data.getDevices().getCameras().get(CAMERA1_DEVICE_ID));
- assertNotNull(data.getDevices().getCameras().get(CAMERA2_DEVICE_ID));
- assertEquals(data.getDevices().getSmokeCoAlarms().size(), 4);
- assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE1_DEVICE_ID));
- assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE2_DEVICE_ID));
- assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE3_DEVICE_ID));
- assertNotNull(data.getDevices().getSmokeCoAlarms().get(SMOKE4_DEVICE_ID));
- }
-
- @Test
- public void verifyThermostat() throws IOException {
- WWNThermostat thermostat = fromJson("thermostat-data.json", WWNThermostat.class);
- logger.debug("Thermostat: {}", thermostat);
-
- assertTrue(thermostat.isOnline());
- assertTrue(thermostat.isCanHeat());
- assertTrue(thermostat.isHasLeaf());
- assertFalse(thermostat.isCanCool());
- assertFalse(thermostat.isFanTimerActive());
- assertFalse(thermostat.isLocked());
- assertFalse(thermostat.isSunlightCorrectionActive());
- assertTrue(thermostat.isSunlightCorrectionEnabled());
- assertFalse(thermostat.isUsingEmergencyHeat());
- assertEquals(THERMOSTAT1_DEVICE_ID, thermostat.getDeviceId());
- assertEquals(Integer.valueOf(15), thermostat.getFanTimerDuration());
- assertEqualDateTime("2017-02-02T21:00:06.000Z", thermostat.getLastConnection());
- assertEqualDateTime("1970-01-01T00:00:00.000Z", thermostat.getFanTimerTimeout());
- assertEquals(Double.valueOf(24.0), thermostat.getEcoTemperatureHigh());
- assertEquals(Double.valueOf(12.5), thermostat.getEcoTemperatureLow());
- assertEquals(Double.valueOf(22.0), thermostat.getLockedTempMax());
- assertEquals(Double.valueOf(20.0), thermostat.getLockedTempMin());
- assertEquals(WWNThermostat.Mode.HEAT, thermostat.getMode());
- assertEquals("Living Room (Living Room)", thermostat.getName());
- assertEquals("Living Room Thermostat (Living Room)", thermostat.getNameLong());
- assertEquals(null, thermostat.getPreviousHvacMode());
- assertEquals("5.6-7", thermostat.getSoftwareVersion());
- assertEquals(WWNThermostat.State.OFF, thermostat.getHvacState());
- assertEquals(STRUCTURE1_STRUCTURE_ID, thermostat.getStructureId());
- assertEquals(Double.valueOf(15.5), thermostat.getTargetTemperature());
- assertEquals(Double.valueOf(24.0), thermostat.getTargetTemperatureHigh());
- assertEquals(Double.valueOf(20.0), thermostat.getTargetTemperatureLow());
- assertEquals(SIUnits.CELSIUS, thermostat.getTemperatureUnit());
- assertEquals(Integer.valueOf(0), thermostat.getTimeToTarget());
- assertEquals(THERMOSTAT1_WHERE_ID, thermostat.getWhereId());
- assertEquals("Living Room", thermostat.getWhereName());
- }
-
- @Test
- public void thermostatTimeToTargetSupportedValueParsing() {
- assertEquals((Integer) 0, WWNThermostat.parseTimeToTarget("~0"));
- assertEquals((Integer) 5, WWNThermostat.parseTimeToTarget("<5"));
- assertEquals((Integer) 10, WWNThermostat.parseTimeToTarget("<10"));
- assertEquals((Integer) 15, WWNThermostat.parseTimeToTarget("~15"));
- assertEquals((Integer) 90, WWNThermostat.parseTimeToTarget("~90"));
- assertEquals((Integer) 120, WWNThermostat.parseTimeToTarget(">120"));
- }
-
- @Test
- public void thermostatTimeToTargetUnsupportedValueParsing() {
- assertThrows(NumberFormatException.class, () -> WWNThermostat.parseTimeToTarget("#5"));
- }
-
- @Test
- public void verifyCamera() throws IOException {
- WWNCamera camera = fromJson("camera-data.json", WWNCamera.class);
- logger.debug("Camera: {}", camera);
-
- assertTrue(camera.isOnline());
- assertEquals("Upstairs", camera.getName());
- assertEquals("Upstairs Camera", camera.getNameLong());
- assertEquals(STRUCTURE1_STRUCTURE_ID, camera.getStructureId());
- assertEquals(CAMERA1_WHERE_ID, camera.getWhereId());
- assertTrue(camera.isAudioInputEnabled());
- assertFalse(camera.isPublicShareEnabled());
- assertFalse(camera.isStreaming());
- assertFalse(camera.isVideoHistoryEnabled());
- assertEquals("https://camera_app_url", camera.getAppUrl());
- assertEquals(CAMERA1_DEVICE_ID, camera.getDeviceId());
- assertNull(camera.getLastConnection());
- assertEqualDateTime("2017-01-22T08:19:20.000Z", camera.getLastIsOnlineChange());
- assertNull(camera.getPublicShareUrl());
- assertEquals("https://camera_snapshot_url", camera.getSnapshotUrl());
- assertEquals("205-600052", camera.getSoftwareVersion());
- assertEquals("https://camera_web_url", camera.getWebUrl());
- assertEquals("https://last_event_animated_image_url", camera.getLastEvent().getAnimatedImageUrl());
- assertEquals(2, camera.getLastEvent().getActivityZones().size());
- assertEquals("id1", camera.getLastEvent().getActivityZones().get(0));
- assertEquals("https://last_event_app_url", camera.getLastEvent().getAppUrl());
- assertEqualDateTime("2017-01-22T07:40:38.680Z", camera.getLastEvent().getEndTime());
- assertEquals("https://last_event_image_url", camera.getLastEvent().getImageUrl());
- assertEqualDateTime("2017-01-22T07:40:19.020Z", camera.getLastEvent().getStartTime());
- assertEqualDateTime("2017-02-05T07:40:19.020Z", camera.getLastEvent().getUrlsExpireTime());
- assertEquals("https://last_event_web_url", camera.getLastEvent().getWebUrl());
- assertTrue(camera.getLastEvent().isHasMotion());
- assertFalse(camera.getLastEvent().isHasPerson());
- assertFalse(camera.getLastEvent().isHasSound());
- }
-
- @Test
- public void verifySmokeDetector() throws IOException {
- WWNSmokeDetector smokeDetector = fromJson("smoke-detector-data.json", WWNSmokeDetector.class);
- logger.debug("SmokeDetector: {}", smokeDetector);
-
- assertTrue(smokeDetector.isOnline());
- assertEquals(SMOKE1_WHERE_ID, smokeDetector.getWhereId());
- assertEquals(SMOKE1_DEVICE_ID, smokeDetector.getDeviceId());
- assertEquals("Downstairs", smokeDetector.getName());
- assertEquals("Downstairs Nest Protect", smokeDetector.getNameLong());
- assertEqualDateTime("2017-02-02T20:53:05.338Z", smokeDetector.getLastConnection());
- assertEquals(WWNSmokeDetector.BatteryHealth.OK, smokeDetector.getBatteryHealth());
- assertEquals(WWNSmokeDetector.AlarmState.OK, smokeDetector.getCoAlarmState());
- assertEquals(WWNSmokeDetector.AlarmState.OK, smokeDetector.getSmokeAlarmState());
- assertEquals("3.1rc9", smokeDetector.getSoftwareVersion());
- assertEquals(STRUCTURE1_STRUCTURE_ID, smokeDetector.getStructureId());
- assertEquals(WWNSmokeDetector.UiColorState.GREEN, smokeDetector.getUiColorState());
- }
-
- @Test
- public void verifyAccessToken() throws IOException {
- WWNAccessTokenData accessToken = fromJson("access-token-data.json", WWNAccessTokenData.class);
- logger.debug("AccessTokenData: {}", accessToken);
-
- assertEquals("access_token", accessToken.getAccessToken());
- assertEquals(Long.valueOf(315360000L), accessToken.getExpiresIn());
- }
-
- @Test
- public void verifyStructure() throws IOException {
- WWNStructure structure = fromJson("structure-data.json", WWNStructure.class);
- logger.debug("Structure: {}", structure);
-
- assertEquals("Home", structure.getName());
- assertEquals("US", structure.getCountryCode());
- assertEquals("98056", structure.getPostalCode());
- assertEquals(WWNStructure.HomeAwayState.HOME, structure.getAway());
- assertEqualDateTime("2017-02-02T03:10:08.000Z", structure.getEtaBegin());
- assertNull(structure.getEta());
- assertNull(structure.getPeakPeriodEndTime());
- assertNull(structure.getPeakPeriodStartTime());
- assertEquals(STRUCTURE1_STRUCTURE_ID, structure.getStructureId());
- assertEquals("America/Los_Angeles", structure.getTimeZone());
- assertFalse(structure.isRhrEnrollment());
- }
-
- @Test
- public void verifyError() throws IOException {
- WWNErrorData error = fromJson("error-data.json", WWNErrorData.class);
- logger.debug("ErrorData: {}", error);
-
- assertEquals("blocked", error.getError());
- assertEquals("https://developer.nest.com/documentation/cloud/error-messages#blocked", error.getType());
- assertEquals("blocked", error.getMessage());
- assertEquals("bb514046-edc9-4bca-8239-f7a3cfb0925a", error.getInstance());
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.handler;
-
-import static org.hamcrest.CoreMatchers.*;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.*;
-
-import javax.ws.rs.client.ClientBuilder;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.openhab.binding.nest.internal.wwn.config.WWNAccountConfiguration;
-import org.openhab.binding.nest.internal.wwn.test.WWNTestAccountHandler;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusInfo;
-import org.openhab.core.thing.binding.ThingHandler;
-import org.openhab.core.thing.binding.ThingHandlerCallback;
-import org.osgi.service.jaxrs.client.SseEventSourceFactory;
-
-/**
- * Tests cases for {@link WWNAccountHandler}.
- *
- * @author David Bennett - Initial contribution
- */
-@ExtendWith(MockitoExtension.class)
-@NonNullByDefault
-public class WWNAccountHandlerTest {
-
- private @NonNullByDefault({}) ThingHandler handler;
-
- private @Mock @NonNullByDefault({}) Bridge bridge;
- private @Mock @NonNullByDefault({}) ThingHandlerCallback callback;
- private @Mock @NonNullByDefault({}) ClientBuilder clientBuilder;
- private @Mock @NonNullByDefault({}) Configuration configuration;
- private @Mock @NonNullByDefault({}) SseEventSourceFactory eventSourceFactory;
-
- @BeforeEach
- public void beforeEach() {
- handler = new WWNTestAccountHandler(bridge, clientBuilder, eventSourceFactory, "http://localhost");
- handler.setCallback(callback);
- }
-
- @Test
- public void initializeShouldCallTheCallback() {
- when(bridge.getConfiguration()).thenReturn(configuration);
- WWNAccountConfiguration bridgeConfig = new WWNAccountConfiguration();
- when(configuration.as(eq(WWNAccountConfiguration.class))).thenReturn(bridgeConfig);
- bridgeConfig.accessToken = "my token";
-
- // we expect the handler#initialize method to call the callback during execution and
- // pass it the thing and a ThingStatusInfo object containing the ThingStatus of the thing.
- handler.initialize();
-
- // the argument captor will capture the argument of type ThingStatusInfo given to the
- // callback#statusUpdated method.
- ArgumentCaptor<ThingStatusInfo> statusInfoCaptor = ArgumentCaptor.forClass(ThingStatusInfo.class);
-
- // verify the interaction with the callback and capture the ThingStatusInfo argument:
- verify(callback).statusUpdated(eq(bridge), statusInfoCaptor.capture());
- // assert that the ThingStatusInfo given to the callback was build with the UNKNOWN status:
- ThingStatusInfo thingStatusInfo = statusInfoCaptor.getValue();
- assertThat(thingStatusInfo.getStatus(), is(equalTo(ThingStatus.UNKNOWN)));
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.handler;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
-import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
-import static org.openhab.core.library.types.OnOffType.*;
-
-import java.io.IOException;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.builder.ThingBuilder;
-
-/**
- * Tests for {@link WWNCameraHandler}.
- *
- * @author Wouter Born - Initial contribution
- */
-@NonNullByDefault
-public class WWNCameraHandlerTest extends WWNThingHandlerOSGiTest {
-
- private static final ThingUID CAMERA_UID = new ThingUID(THING_TYPE_CAMERA, "camera1");
- private static final int CHANNEL_COUNT = 20;
-
- public WWNCameraHandlerTest() {
- super(WWNCameraHandler.class);
- }
-
- @Override
- protected Thing buildThing(Bridge bridge) {
- Map<String, Object> properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, CAMERA1_DEVICE_ID);
-
- return ThingBuilder.create(THING_TYPE_CAMERA, CAMERA_UID).withLabel("Test Camera").withBridge(bridge.getUID())
- .withChannels(buildChannels(THING_TYPE_CAMERA, CAMERA_UID))
- .withConfiguration(new Configuration(properties)).build();
- }
-
- @Test
- public void completeCameraUpdate() throws IOException {
- assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
- assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
-
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- // Camera channel group
- assertThatItemHasState(CHANNEL_CAMERA_APP_URL, new StringType("https://camera_app_url"));
- assertThatItemHasState(CHANNEL_CAMERA_AUDIO_INPUT_ENABLED, ON);
- assertThatItemHasState(CHANNEL_CAMERA_LAST_ONLINE_CHANGE, parseDateTimeType("2017-01-22T08:19:20.000Z"));
- assertThatItemHasState(CHANNEL_CAMERA_PUBLIC_SHARE_ENABLED, OFF);
- assertThatItemHasState(CHANNEL_CAMERA_PUBLIC_SHARE_URL, new StringType("https://camera_public_share_url"));
- assertThatItemHasState(CHANNEL_CAMERA_SNAPSHOT_URL, new StringType("https://camera_snapshot_url"));
- assertThatItemHasState(CHANNEL_CAMERA_STREAMING, OFF);
- assertThatItemHasState(CHANNEL_CAMERA_VIDEO_HISTORY_ENABLED, OFF);
- assertThatItemHasState(CHANNEL_CAMERA_WEB_URL, new StringType("https://camera_web_url"));
-
- // Last event channel group
- assertThatItemHasState(CHANNEL_LAST_EVENT_ACTIVITY_ZONES, new StringType("id1,id2"));
- assertThatItemHasState(CHANNEL_LAST_EVENT_ANIMATED_IMAGE_URL,
- new StringType("https://last_event_animated_image_url"));
- assertThatItemHasState(CHANNEL_LAST_EVENT_APP_URL, new StringType("https://last_event_app_url"));
- assertThatItemHasState(CHANNEL_LAST_EVENT_END_TIME, parseDateTimeType("2017-01-22T07:40:38.680Z"));
- assertThatItemHasState(CHANNEL_LAST_EVENT_HAS_MOTION, ON);
- assertThatItemHasState(CHANNEL_LAST_EVENT_HAS_PERSON, OFF);
- assertThatItemHasState(CHANNEL_LAST_EVENT_HAS_SOUND, OFF);
- assertThatItemHasState(CHANNEL_LAST_EVENT_IMAGE_URL, new StringType("https://last_event_image_url"));
- assertThatItemHasState(CHANNEL_LAST_EVENT_START_TIME, parseDateTimeType("2017-01-22T07:40:19.020Z"));
- assertThatItemHasState(CHANNEL_LAST_EVENT_URLS_EXPIRE_TIME, parseDateTimeType("2017-02-05T07:40:19.020Z"));
- assertThatItemHasState(CHANNEL_LAST_EVENT_WEB_URL, new StringType("https://last_event_web_url"));
-
- assertThatAllItemStatesAreNotNull();
- }
-
- @Test
- public void incompleteCameraUpdate() throws IOException {
- assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
- assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
-
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
- assertThatAllItemStatesAreNotNull();
-
- putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.UNKNOWN)));
- assertThatAllItemStatesAreNull();
- }
-
- @Test
- public void cameraGone() throws IOException {
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
- assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
- }
-
- @Test
- public void channelRefresh() throws IOException {
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
- assertThatAllItemStatesAreNotNull();
-
- updateAllItemStatesToNull();
- assertThatAllItemStatesAreNull();
-
- refreshAllChannels();
- assertThatAllItemStatesAreNotNull();
- }
-
- @Test
- public void handleStreamingCommands() throws IOException {
- handleCommand(CHANNEL_CAMERA_STREAMING, ON);
- assertNestApiPropertyState(CAMERA1_DEVICE_ID, "is_streaming", "true");
-
- handleCommand(CHANNEL_CAMERA_STREAMING, OFF);
- assertNestApiPropertyState(CAMERA1_DEVICE_ID, "is_streaming", "false");
-
- handleCommand(CHANNEL_CAMERA_STREAMING, ON);
- assertNestApiPropertyState(CAMERA1_DEVICE_ID, "is_streaming", "true");
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.handler;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
-import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
-import static org.openhab.core.library.types.OnOffType.OFF;
-
-import java.io.IOException;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.builder.ThingBuilder;
-
-/**
- * Tests for {@link WWNSmokeDetectorHandler}.
- *
- * @author Wouter Born - Initial contribution
- */
-@NonNullByDefault
-public class WWNSmokeDetectorHandlerTest extends WWNThingHandlerOSGiTest {
-
- private static final ThingUID SMOKE_DETECTOR_UID = new ThingUID(THING_TYPE_SMOKE_DETECTOR, "smoke1");
- private static final int CHANNEL_COUNT = 7;
-
- public WWNSmokeDetectorHandlerTest() {
- super(WWNSmokeDetectorHandler.class);
- }
-
- @Override
- protected Thing buildThing(Bridge bridge) {
- Map<String, Object> properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, SMOKE1_DEVICE_ID);
-
- return ThingBuilder.create(THING_TYPE_SMOKE_DETECTOR, SMOKE_DETECTOR_UID).withLabel("Test Smoke Detector")
- .withBridge(bridge.getUID()).withChannels(buildChannels(THING_TYPE_SMOKE_DETECTOR, SMOKE_DETECTOR_UID))
- .withConfiguration(new Configuration(properties)).build();
- }
-
- @Test
- public void completeSmokeDetectorUpdate() throws IOException {
- assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
- assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
-
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- assertThatItemHasState(CHANNEL_CO_ALARM_STATE, new StringType("OK"));
- assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T20:53:05.338Z"));
- assertThatItemHasState(CHANNEL_LAST_MANUAL_TEST_TIME, parseDateTimeType("2016-10-31T23:59:59.000Z"));
- assertThatItemHasState(CHANNEL_LOW_BATTERY, OFF);
- assertThatItemHasState(CHANNEL_MANUAL_TEST_ACTIVE, OFF);
- assertThatItemHasState(CHANNEL_SMOKE_ALARM_STATE, new StringType("OK"));
- assertThatItemHasState(CHANNEL_UI_COLOR_STATE, new StringType("GREEN"));
-
- assertThatAllItemStatesAreNotNull();
- }
-
- @Test
- public void incompleteSmokeDetectorUpdate() throws IOException {
- assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
- assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
-
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
- assertThatAllItemStatesAreNotNull();
-
- putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.UNKNOWN)));
- assertThatAllItemStatesAreNull();
- }
-
- @Test
- public void smokeDetectorGone() throws IOException {
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
- assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
- }
-
- @Test
- public void channelRefresh() throws IOException {
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
- assertThatAllItemStatesAreNotNull();
-
- updateAllItemStatesToNull();
- assertThatAllItemStatesAreNull();
-
- refreshAllChannels();
- assertThatAllItemStatesAreNotNull();
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.handler;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
-import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
-import static org.openhab.core.library.types.OnOffType.OFF;
-
-import java.io.IOException;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.nest.internal.wwn.config.WWNStructureConfiguration;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.builder.ThingBuilder;
-
-/**
- * Tests for {@link WWNStructureHandler}.
- *
- * @author Wouter Born - Initial contribution
- */
-@NonNullByDefault
-public class WWNStructureHandlerTest extends WWNThingHandlerOSGiTest {
-
- private static final ThingUID STRUCTURE_UID = new ThingUID(THING_TYPE_STRUCTURE, "structure1");
- private static final int CHANNEL_COUNT = 11;
-
- public WWNStructureHandlerTest() {
- super(WWNStructureHandler.class);
- }
-
- @Override
- protected Thing buildThing(Bridge bridge) {
- Map<String, Object> properties = Map.of(WWNStructureConfiguration.STRUCTURE_ID, STRUCTURE1_STRUCTURE_ID);
-
- return ThingBuilder.create(THING_TYPE_STRUCTURE, STRUCTURE_UID).withLabel("Test Structure")
- .withBridge(bridge.getUID()).withChannels(buildChannels(THING_TYPE_STRUCTURE, STRUCTURE_UID))
- .withConfiguration(new Configuration(properties)).build();
- }
-
- @Test
- public void completeStructureUpdate() throws IOException {
- assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
- assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
-
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- assertThatItemHasState(CHANNEL_AWAY, new StringType("HOME"));
- assertThatItemHasState(CHANNEL_CO_ALARM_STATE, new StringType("OK"));
- assertThatItemHasState(CHANNEL_COUNTRY_CODE, new StringType("US"));
- assertThatItemHasState(CHANNEL_ETA_BEGIN, parseDateTimeType("2017-02-02T03:10:08.000Z"));
- assertThatItemHasState(CHANNEL_PEAK_PERIOD_END_TIME, parseDateTimeType("2017-07-01T01:03:08.400Z"));
- assertThatItemHasState(CHANNEL_PEAK_PERIOD_START_TIME, parseDateTimeType("2017-06-01T13:31:10.870Z"));
- assertThatItemHasState(CHANNEL_POSTAL_CODE, new StringType("98056"));
- assertThatItemHasState(CHANNEL_RUSH_HOUR_REWARDS_ENROLLMENT, OFF);
- assertThatItemHasState(CHANNEL_SECURITY_STATE, new StringType("OK"));
- assertThatItemHasState(CHANNEL_SMOKE_ALARM_STATE, new StringType("OK"));
- assertThatItemHasState(CHANNEL_TIME_ZONE, new StringType("America/Los_Angeles"));
-
- assertThatAllItemStatesAreNotNull();
- }
-
- @Test
- public void incompleteStructureUpdate() throws IOException {
- assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
- assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
-
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
- assertThatAllItemStatesAreNotNull();
-
- putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
- assertThatAllItemStatesAreNull();
- }
-
- @Test
- public void structureGone() throws IOException {
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
- assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
- }
-
- @Test
- public void channelRefresh() throws IOException {
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
- assertThatAllItemStatesAreNotNull();
-
- updateAllItemStatesToNull();
- assertThatAllItemStatesAreNull();
-
- refreshAllChannels();
- assertThatAllItemStatesAreNotNull();
- }
-
- @Test
- public void handleAwayCommands() throws IOException {
- handleCommand(CHANNEL_AWAY, new StringType("AWAY"));
- assertNestApiPropertyState(STRUCTURE1_STRUCTURE_ID, "away", "away");
-
- handleCommand(CHANNEL_AWAY, new StringType("HOME"));
- assertNestApiPropertyState(STRUCTURE1_STRUCTURE_ID, "away", "home");
-
- handleCommand(CHANNEL_AWAY, new StringType("AWAY"));
- assertNestApiPropertyState(STRUCTURE1_STRUCTURE_ID, "away", "away");
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.handler;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
-import static org.openhab.binding.nest.internal.wwn.dto.WWNDataUtil.*;
-import static org.openhab.core.library.types.OnOffType.*;
-import static org.openhab.core.library.unit.ImperialUnits.FAHRENHEIT;
-import static org.openhab.core.library.unit.SIUnits.CELSIUS;
-
-import java.io.IOException;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.nest.internal.wwn.config.WWNDeviceConfiguration;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.library.unit.Units;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.builder.ThingBuilder;
-
-/**
- * Tests for {@link WWNThermostatHandler}.
- *
- * @author Wouter Born - Initial contribution
- */
-@NonNullByDefault
-public class WWNThermostatHandlerTest extends WWNThingHandlerOSGiTest {
-
- private static final ThingUID THERMOSTAT_UID = new ThingUID(THING_TYPE_THERMOSTAT, "thermostat1");
- private static final int CHANNEL_COUNT = 25;
-
- public WWNThermostatHandlerTest() {
- super(WWNThermostatHandler.class);
- }
-
- @Override
- protected Thing buildThing(Bridge bridge) {
- Map<String, Object> properties = Map.of(WWNDeviceConfiguration.DEVICE_ID, THERMOSTAT1_DEVICE_ID);
-
- return ThingBuilder.create(THING_TYPE_THERMOSTAT, THERMOSTAT_UID).withLabel("Test Thermostat")
- .withBridge(bridge.getUID()).withChannels(buildChannels(THING_TYPE_THERMOSTAT, THERMOSTAT_UID))
- .withConfiguration(new Configuration(properties)).build();
- }
-
- @Test
- public void completeThermostatCelsiusUpdate() throws IOException {
- assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
- assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
-
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, CELSIUS));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- assertThatItemHasState(CHANNEL_CAN_COOL, OFF);
- assertThatItemHasState(CHANNEL_CAN_HEAT, ON);
- assertThatItemHasState(CHANNEL_ECO_MAX_SET_POINT, new QuantityType<>(24, CELSIUS));
- assertThatItemHasState(CHANNEL_ECO_MIN_SET_POINT, new QuantityType<>(12.5, CELSIUS));
- assertThatItemHasState(CHANNEL_FAN_TIMER_ACTIVE, OFF);
- assertThatItemHasState(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(15, Units.MINUTE));
- assertThatItemHasState(CHANNEL_FAN_TIMER_TIMEOUT, parseDateTimeType("1970-01-01T00:00:00.000Z"));
- assertThatItemHasState(CHANNEL_HAS_FAN, ON);
- assertThatItemHasState(CHANNEL_HAS_LEAF, ON);
- assertThatItemHasState(CHANNEL_HUMIDITY, new QuantityType<>(25, Units.PERCENT));
- assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T21:00:06.000Z"));
- assertThatItemHasState(CHANNEL_LOCKED, OFF);
- assertThatItemHasState(CHANNEL_LOCKED_MAX_SET_POINT, new QuantityType<>(22, CELSIUS));
- assertThatItemHasState(CHANNEL_LOCKED_MIN_SET_POINT, new QuantityType<>(20, CELSIUS));
- assertThatItemHasState(CHANNEL_MAX_SET_POINT, new QuantityType<>(24, CELSIUS));
- assertThatItemHasState(CHANNEL_MIN_SET_POINT, new QuantityType<>(20, CELSIUS));
- assertThatItemHasState(CHANNEL_MODE, new StringType("HEAT"));
- assertThatItemHasState(CHANNEL_PREVIOUS_MODE, new StringType("HEAT"));
- assertThatItemHasState(CHANNEL_SET_POINT, new QuantityType<>(15.5, CELSIUS));
- assertThatItemHasState(CHANNEL_STATE, new StringType("OFF"));
- assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ACTIVE, OFF);
- assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ENABLED, ON);
- assertThatItemHasState(CHANNEL_TEMPERATURE, new QuantityType<>(19, CELSIUS));
- assertThatItemHasState(CHANNEL_TIME_TO_TARGET, new QuantityType<>(0, Units.MINUTE));
- assertThatItemHasState(CHANNEL_USING_EMERGENCY_HEAT, OFF);
-
- assertThatAllItemStatesAreNotNull();
- }
-
- @Test
- public void completeThermostatFahrenheitUpdate() throws IOException {
- assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
- assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
-
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, FAHRENHEIT));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- assertThatItemHasState(CHANNEL_CAN_COOL, OFF);
- assertThatItemHasState(CHANNEL_CAN_HEAT, ON);
- assertThatItemHasState(CHANNEL_ECO_MAX_SET_POINT, new QuantityType<>(76, FAHRENHEIT));
- assertThatItemHasState(CHANNEL_ECO_MIN_SET_POINT, new QuantityType<>(55, FAHRENHEIT));
- assertThatItemHasState(CHANNEL_FAN_TIMER_ACTIVE, OFF);
- assertThatItemHasState(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(15, Units.MINUTE));
- assertThatItemHasState(CHANNEL_FAN_TIMER_TIMEOUT, parseDateTimeType("1970-01-01T00:00:00.000Z"));
- assertThatItemHasState(CHANNEL_HAS_FAN, ON);
- assertThatItemHasState(CHANNEL_HAS_LEAF, ON);
- assertThatItemHasState(CHANNEL_HUMIDITY, new QuantityType<>(25, Units.PERCENT));
- assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T21:00:06.000Z"));
- assertThatItemHasState(CHANNEL_LOCKED, OFF);
- assertThatItemHasState(CHANNEL_LOCKED_MAX_SET_POINT, new QuantityType<>(72, FAHRENHEIT));
- assertThatItemHasState(CHANNEL_LOCKED_MIN_SET_POINT, new QuantityType<>(68, FAHRENHEIT));
- assertThatItemHasState(CHANNEL_MAX_SET_POINT, new QuantityType<>(75, FAHRENHEIT));
- assertThatItemHasState(CHANNEL_MIN_SET_POINT, new QuantityType<>(68, FAHRENHEIT));
- assertThatItemHasState(CHANNEL_MODE, new StringType("HEAT"));
- assertThatItemHasState(CHANNEL_PREVIOUS_MODE, new StringType("HEAT"));
- assertThatItemHasState(CHANNEL_SET_POINT, new QuantityType<>(60, FAHRENHEIT));
- assertThatItemHasState(CHANNEL_STATE, new StringType("OFF"));
- assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ACTIVE, OFF);
- assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ENABLED, ON);
- assertThatItemHasState(CHANNEL_TEMPERATURE, new QuantityType<>(66, FAHRENHEIT));
- assertThatItemHasState(CHANNEL_TIME_TO_TARGET, new QuantityType<>(0, Units.MINUTE));
- assertThatItemHasState(CHANNEL_USING_EMERGENCY_HEAT, OFF);
-
- assertThatAllItemStatesAreNotNull();
- }
-
- @Test
- public void incompleteThermostatUpdate() throws IOException {
- assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
- assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
-
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
- assertThatAllItemStatesAreNotNull();
-
- putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.UNKNOWN)));
- assertThatAllItemStatesAreNull();
- }
-
- @Test
- public void thermostatGone() throws IOException {
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
- assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
- }
-
- @Test
- public void channelRefresh() throws IOException {
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
- assertThatAllItemStatesAreNotNull();
-
- updateAllItemStatesToNull();
- assertThatAllItemStatesAreNull();
-
- refreshAllChannels();
- assertThatAllItemStatesAreNotNull();
- }
-
- @Test
- public void handleFanTimerActiveCommands() throws IOException {
- handleCommand(CHANNEL_FAN_TIMER_ACTIVE, ON);
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "true");
-
- handleCommand(CHANNEL_FAN_TIMER_ACTIVE, OFF);
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "false");
-
- handleCommand(CHANNEL_FAN_TIMER_ACTIVE, ON);
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "true");
- }
-
- @Test
- public void handleFanTimerDurationCommands() throws IOException {
- int[] durations = { 15, 30, 45, 60, 120, 240, 480, 960, 15 };
- for (int duration : durations) {
- handleCommand(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(duration, Units.MINUTE));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_duration", String.valueOf(duration));
- }
- }
-
- @Test
- public void handleMaxSetPointCelsiusCommands() throws IOException {
- celsiusCommandsTest(CHANNEL_MAX_SET_POINT, "target_temperature_high_c");
- }
-
- @Test
- public void handleMaxSetPointFahrenheitCommands() throws IOException {
- fahrenheitCommandsTest(CHANNEL_MAX_SET_POINT, "target_temperature_high_f");
- }
-
- @Test
- public void handleMinSetPointCelsiusCommands() throws IOException {
- celsiusCommandsTest(CHANNEL_MIN_SET_POINT, "target_temperature_low_c");
- }
-
- @Test
- public void handleMinSetPointFahrenheitCommands() throws IOException {
- fahrenheitCommandsTest(CHANNEL_MIN_SET_POINT, "target_temperature_low_f");
- }
-
- @Test
- public void handleChannelModeCommands() throws IOException {
- handleCommand(CHANNEL_MODE, new StringType("HEAT"));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat");
-
- handleCommand(CHANNEL_MODE, new StringType("COOL"));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "cool");
-
- handleCommand(CHANNEL_MODE, new StringType("HEAT_COOL"));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat-cool");
-
- handleCommand(CHANNEL_MODE, new StringType("ECO"));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "eco");
-
- handleCommand(CHANNEL_MODE, new StringType("OFF"));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "off");
-
- handleCommand(CHANNEL_MODE, new StringType("HEAT"));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat");
- }
-
- @Test
- public void handleSetPointCelsiusCommands() throws IOException {
- celsiusCommandsTest(CHANNEL_SET_POINT, "target_temperature_c");
- }
-
- @Test
- public void handleSetPointFahrenheitCommands() throws IOException {
- fahrenheitCommandsTest(CHANNEL_SET_POINT, "target_temperature_f");
- }
-
- private void celsiusCommandsTest(String channelId, String apiPropertyName) throws IOException {
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, CELSIUS));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- handleCommand(channelId, new QuantityType<>(20, CELSIUS));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "20.0");
-
- handleCommand(channelId, new QuantityType<>(21.123, CELSIUS));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "21.0");
-
- handleCommand(channelId, new QuantityType<>(22.541, CELSIUS));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "22.5");
-
- handleCommand(channelId, new QuantityType<>(23.74, CELSIUS));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "23.5");
-
- handleCommand(channelId, new QuantityType<>(23.75, CELSIUS));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "24.0");
-
- handleCommand(channelId, new QuantityType<>(70, FAHRENHEIT));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "21.0");
- }
-
- private void fahrenheitCommandsTest(String channelId, String apiPropertyName) throws IOException {
- waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
- putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, FAHRENHEIT));
- waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
-
- handleCommand(channelId, new QuantityType<>(70, FAHRENHEIT));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "70");
-
- handleCommand(channelId, new QuantityType<>(71.123, FAHRENHEIT));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "71");
-
- handleCommand(channelId, new QuantityType<>(71.541, FAHRENHEIT));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "72");
-
- handleCommand(channelId, new QuantityType<>(72.74, FAHRENHEIT));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "73");
-
- handleCommand(channelId, new QuantityType<>(73.75, FAHRENHEIT));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "74");
-
- handleCommand(channelId, new QuantityType<>(21, CELSIUS));
- assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "70");
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.handler;
-
-import static java.util.Map.entry;
-import static org.hamcrest.CoreMatchers.*;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-import static org.hamcrest.core.IsNot.not;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.*;
-import static org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient.PUT;
-
-import java.io.IOException;
-import java.time.Instant;
-import java.time.format.DateTimeParseException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.TimeZone;
-import java.util.function.Function;
-
-import javax.ws.rs.client.ClientBuilder;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
-import org.mockito.ArgumentMatchers;
-import org.openhab.binding.nest.internal.wwn.config.WWNAccountConfiguration;
-import org.openhab.binding.nest.internal.wwn.test.WWNTestAccountHandler;
-import org.openhab.binding.nest.internal.wwn.test.WWNTestApiServlet;
-import org.openhab.binding.nest.internal.wwn.test.WWNTestHandlerFactory;
-import org.openhab.binding.nest.internal.wwn.test.WWNTestServer;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.events.EventPublisher;
-import org.openhab.core.items.Item;
-import org.openhab.core.items.ItemFactory;
-import org.openhab.core.items.ItemNotFoundException;
-import org.openhab.core.items.ItemRegistry;
-import org.openhab.core.items.events.ItemEventFactory;
-import org.openhab.core.library.types.DateTimeType;
-import org.openhab.core.test.TestPortUtil;
-import org.openhab.core.test.java.JavaOSGiTest;
-import org.openhab.core.test.storage.VolatileStorageService;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.Channel;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.ManagedThingProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingProvider;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.ThingHandlerFactory;
-import org.openhab.core.thing.binding.ThingTypeProvider;
-import org.openhab.core.thing.binding.builder.BridgeBuilder;
-import org.openhab.core.thing.binding.builder.ChannelBuilder;
-import org.openhab.core.thing.link.ItemChannelLink;
-import org.openhab.core.thing.link.ManagedItemChannelLinkProvider;
-import org.openhab.core.thing.type.ChannelDefinition;
-import org.openhab.core.thing.type.ChannelGroupDefinition;
-import org.openhab.core.thing.type.ChannelGroupType;
-import org.openhab.core.thing.type.ChannelGroupTypeRegistry;
-import org.openhab.core.thing.type.ChannelType;
-import org.openhab.core.thing.type.ChannelTypeRegistry;
-import org.openhab.core.thing.type.ThingType;
-import org.openhab.core.thing.type.ThingTypeRegistry;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.osgi.service.component.ComponentContext;
-import org.osgi.service.jaxrs.client.SseEventSourceFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * {@link WWNThingHandlerOSGiTest} is an abstract base class for Nest OSGi based tests.
- *
- * @author Wouter Born - Initial contribution
- */
-@NonNullByDefault
-public abstract class WWNThingHandlerOSGiTest extends JavaOSGiTest {
-
- private static final String SERVER_HOST = "127.0.0.1";
- private static final int SERVER_PORT = TestPortUtil.findFreePort();
- private static final int SERVER_TIMEOUT = -1;
- private static final String REDIRECT_URL = "http://" + SERVER_HOST + ":" + SERVER_PORT;
-
- private final Logger logger = LoggerFactory.getLogger(WWNThingHandlerOSGiTest.class);
-
- private static @Nullable WWNTestServer server;
- private static WWNTestApiServlet servlet = new WWNTestApiServlet();
-
- private @NonNullByDefault({}) ChannelTypeRegistry channelTypeRegistry;
- private @NonNullByDefault({}) ChannelGroupTypeRegistry channelGroupTypeRegistry;
- private @NonNullByDefault({}) ItemFactory itemFactory;
- private @NonNullByDefault({}) ItemRegistry itemRegistry;
- private @NonNullByDefault({}) EventPublisher eventPublisher;
- private @NonNullByDefault({}) ManagedThingProvider managedThingProvider;
- private @NonNullByDefault({}) ThingTypeRegistry thingTypeRegistry;
- private @NonNullByDefault({}) ManagedItemChannelLinkProvider managedItemChannelLinkProvider;
- private @NonNullByDefault({}) VolatileStorageService volatileStorageService = new VolatileStorageService();
-
- protected @NonNullByDefault({}) Bridge bridge;
- protected @NonNullByDefault({}) WWNTestAccountHandler bridgeHandler;
- protected @NonNullByDefault({}) Thing thing;
- protected @NonNullByDefault({}) WWNBaseHandler<?> thingHandler;
- private Class<? extends WWNBaseHandler<?>> thingClass;
-
- private @NonNullByDefault({}) WWNTestHandlerFactory nestTestHandlerFactory;
- private @NonNullByDefault({}) ClientBuilder clientBuilder;
- private @NonNullByDefault({}) SseEventSourceFactory eventSourceFactory;
-
- public WWNThingHandlerOSGiTest(Class<? extends WWNBaseHandler<?>> thingClass) {
- this.thingClass = thingClass;
- }
-
- @BeforeAll
- public static void setUpClass() throws Exception {
- ServletHolder holder = new ServletHolder(servlet);
- server = new WWNTestServer(SERVER_HOST, SERVER_PORT, SERVER_TIMEOUT, holder);
- server.startServer();
- }
-
- @AfterAll
- public static void tearDownClass() throws Exception {
- WWNTestServer testServer = server;
- if (testServer != null) {
- testServer.stopServer();
- }
- }
-
- @BeforeEach
- public void setUp() throws ItemNotFoundException {
- registerService(volatileStorageService);
-
- managedThingProvider = Objects.requireNonNull(getService(ThingProvider.class, ManagedThingProvider.class),
- "Could not get ManagedThingProvider");
- thingTypeRegistry = Objects.requireNonNull(getService(ThingTypeRegistry.class),
- "Could not get ThingTypeRegistry");
- channelTypeRegistry = Objects.requireNonNull(getService(ChannelTypeRegistry.class),
- "Could not get ChannelTypeRegistry");
- channelGroupTypeRegistry = Objects.requireNonNull(getService(ChannelGroupTypeRegistry.class),
- "Could not get ChannelGroupTypeRegistry");
- eventPublisher = Objects.requireNonNull(getService(EventPublisher.class), "Could not get EventPublisher");
- itemFactory = Objects.requireNonNull(getService(ItemFactory.class), "Could not get ItemFactory");
- itemRegistry = Objects.requireNonNull(getService(ItemRegistry.class), "Could not get ItemRegistry");
- managedItemChannelLinkProvider = Objects.requireNonNull(getService(ManagedItemChannelLinkProvider.class),
- "Could not get ManagedItemChannelLinkProvider");
- clientBuilder = Objects.requireNonNull(getService(ClientBuilder.class), "Could not get ClientBuilder");
- eventSourceFactory = Objects.requireNonNull(getService(SseEventSourceFactory.class),
- "Could not get SseEventSourceFactory");
-
- ComponentContext componentContext = mock(ComponentContext.class);
- when(componentContext.getBundleContext()).thenReturn(bundleContext);
-
- nestTestHandlerFactory = new WWNTestHandlerFactory(clientBuilder, eventSourceFactory);
- nestTestHandlerFactory.activate(componentContext,
- Map.of(WWNTestHandlerFactory.REDIRECT_URL_CONFIG_PROPERTY, REDIRECT_URL));
- registerService(nestTestHandlerFactory);
-
- ThingTypeProvider thingTypeProvider = mock(ThingTypeProvider.class);
- when(thingTypeProvider.getThingType(ArgumentMatchers.any(ThingTypeUID.class), nullable(Locale.class)))
- .thenReturn(mock(ThingType.class));
- registerService(thingTypeProvider);
-
- nestTestHandlerFactory = Objects.requireNonNull(
- getService(ThingHandlerFactory.class, WWNTestHandlerFactory.class),
- "Could not get NestTestHandlerFactory");
-
- bridge = buildBridge();
- thing = buildThing(bridge);
-
- bridgeHandler = addThing(bridge, WWNTestAccountHandler.class);
- thingHandler = addThing(thing, thingClass);
-
- createAndLinkItems();
- assertThatAllItemStatesAreNull();
- }
-
- @AfterEach
- public void tearDown() {
- servlet.reset();
- servlet.closeConnections();
-
- if (thing != null) {
- managedThingProvider.remove(thing.getUID());
- }
- if (bridge != null) {
- managedThingProvider.remove(bridge.getUID());
- }
-
- unregisterService(volatileStorageService);
- }
-
- protected Bridge buildBridge() {
- Map<String, Object> properties = Map.ofEntries( //
- entry(WWNAccountConfiguration.ACCESS_TOKEN,
- "c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc"),
- entry(WWNAccountConfiguration.PINCODE, "64P2XRYT"),
- entry(WWNAccountConfiguration.PRODUCT_ID, "8fdf9885-ca07-4252-1aa3-f3d5ca9589e0"),
- entry(WWNAccountConfiguration.PRODUCT_SECRET, "QITLR3iyUlWaj9dbvCxsCKp4f"));
-
- return BridgeBuilder.create(WWNTestAccountHandler.THING_TYPE_TEST_BRIDGE, "test_account")
- .withLabel("Test Account").withConfiguration(new Configuration(properties)).build();
- }
-
- protected abstract Thing buildThing(Bridge bridge);
-
- protected List<Channel> buildChannels(ThingTypeUID thingTypeUID, ThingUID thingUID) {
- waitForAssert(() -> assertThat(thingTypeRegistry.getThingType(thingTypeUID), notNullValue()));
-
- ThingType thingType = thingTypeRegistry.getThingType(thingTypeUID);
-
- List<Channel> channels = new ArrayList<>(buildChannels(thingUID, thingType.getChannelDefinitions(), id -> id));
- for (ChannelGroupDefinition channelGroupDefinition : thingType.getChannelGroupDefinitions()) {
- ChannelGroupType channelGroupType = channelGroupTypeRegistry
- .getChannelGroupType(channelGroupDefinition.getTypeUID());
- String groupId = channelGroupDefinition.getId();
- if (channelGroupType != null) {
- channels.addAll(
- buildChannels(thingUID, channelGroupType.getChannelDefinitions(), id -> groupId + "#" + id));
- }
- }
-
- channels.sort((Channel c1, Channel c2) -> c1.getUID().getId().compareTo(c2.getUID().getId()));
- return channels;
- }
-
- protected List<Channel> buildChannels(ThingUID thingUID, List<ChannelDefinition> channelDefinitions,
- Function<String, String> channelIdFunction) {
- List<Channel> result = new ArrayList<>();
- for (ChannelDefinition channelDefinition : channelDefinitions) {
- ChannelType channelType = channelTypeRegistry.getChannelType(channelDefinition.getChannelTypeUID());
- if (channelType != null) {
- result.add(ChannelBuilder
- .create(new ChannelUID(thingUID, channelIdFunction.apply(channelDefinition.getId())),
- channelType.getItemType())
- .build());
- }
- }
- return result;
- }
-
- @SuppressWarnings("unchecked")
- protected <T> T addThing(Thing thing, Class<T> thingHandlerClass) {
- assertThat(thing.getHandler(), is(nullValue()));
- managedThingProvider.add(thing);
- waitForAssert(() -> assertThat(thing.getHandler(), notNullValue()));
- assertThat(thing.getConfiguration(), is(notNullValue()));
- assertThat(thing.getHandler(), is(instanceOf(thingHandlerClass)));
- return (T) thing.getHandler();
- }
-
- protected String getThingId() {
- return thing.getUID().getId();
- }
-
- protected ThingUID getThingUID() {
- return thing.getUID();
- }
-
- protected void putStreamingEventData(String json) throws IOException {
- String singleLineJson = json.replaceAll("\n\r\\s+", "").replaceAll("\n\\s+", "").replaceAll("\n\r", "")
- .replaceAll("\n", "");
- servlet.queueEvent(PUT, singleLineJson);
- }
-
- protected void createAndLinkItems() {
- thing.getChannels().forEach(c -> {
- String itemName = getItemName(c.getUID().getId());
- Item item = itemFactory.createItem(c.getAcceptedItemType(), itemName);
- if (item != null) {
- itemRegistry.add(item);
- }
- managedItemChannelLinkProvider.add(new ItemChannelLink(itemName, c.getUID()));
- });
- }
-
- protected void assertThatItemHasState(String channelId, State state) {
- waitForAssert(() -> assertThat("Wrong state for item of channel '" + channelId + "' ", getItemState(channelId),
- is(state)));
- }
-
- protected void assertThatItemHasNotState(String channelId, State state) {
- waitForAssert(() -> assertThat("Wrong state for item of channel '" + channelId + "' ", getItemState(channelId),
- is(not(state))));
- }
-
- protected void assertThatAllItemStatesAreNull() {
- thing.getChannels().forEach(c -> assertThatItemHasState(c.getUID().getId(), UnDefType.NULL));
- }
-
- protected void assertThatAllItemStatesAreNotNull() {
- thing.getChannels().forEach(c -> assertThatItemHasNotState(c.getUID().getId(), UnDefType.NULL));
- }
-
- protected ChannelUID getChannelUID(String channelId) {
- return new ChannelUID(getThingUID(), channelId);
- }
-
- protected String getItemName(String channelId) {
- return getThingId() + "_" + channelId.replaceAll("#", "_");
- }
-
- private State getItemState(String channelId) {
- String itemName = getItemName(channelId);
- try {
- return itemRegistry.getItem(itemName).getState();
- } catch (ItemNotFoundException e) {
- throw new AssertionError("Item with name '" + itemName + "' not found");
- }
- }
-
- protected void logItemStates() {
- thing.getChannels().forEach(c -> {
- String channelId = c.getUID().getId();
- String itemName = getItemName(channelId);
- logger.debug("{} = {}", itemName, getItemState(channelId));
- });
- }
-
- protected void updateAllItemStatesToNull() {
- thing.getChannels().forEach(c -> updateItemState(c.getUID().getId(), UnDefType.NULL));
- }
-
- protected void refreshAllChannels() {
- thing.getChannels().forEach(c -> thingHandler.handleCommand(c.getUID(), RefreshType.REFRESH));
- }
-
- protected void handleCommand(String channelId, Command command) {
- thingHandler.handleCommand(getChannelUID(channelId), command);
- }
-
- protected void updateItemState(String channelId, State state) {
- String itemName = getItemName(channelId);
- eventPublisher.post(ItemEventFactory.createStateEvent(itemName, state));
- }
-
- protected void assertNestApiPropertyState(String nestId, String propertyName, String state) {
- waitForAssert(() -> assertThat(servlet.getNestIdPropertyState(nestId, propertyName), is(state)));
- }
-
- public static DateTimeType parseDateTimeType(String text) {
- try {
- return new DateTimeType(Instant.parse(text).atZone(TimeZone.getDefault().toZoneId()));
- } catch (DateTimeParseException e) {
- throw new IllegalArgumentException("Invalid date time argument: " + text, e);
- }
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.test;
-
-import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.BINDING_ID;
-
-import java.util.Properties;
-import java.util.Set;
-
-import javax.ws.rs.client.ClientBuilder;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.nest.internal.wwn.exceptions.InvalidWWNAccessTokenException;
-import org.openhab.binding.nest.internal.wwn.handler.WWNAccountHandler;
-import org.openhab.binding.nest.internal.wwn.handler.WWNRedirectUrlSupplier;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ThingTypeUID;
-import org.osgi.service.jaxrs.client.SseEventSourceFactory;
-
-/**
- * The {@link WWNTestAccountHandler} is a {@link WWNAccountHandler} modified for testing. Using the
- * {@link NestTestRedirectUrlSupplier} it will always connect to same provided {@link #redirectUrl}.
- *
- * @author Wouter Born - Initial contribution
- */
-@NonNullByDefault
-public class WWNTestAccountHandler extends WWNAccountHandler {
-
- class NestTestRedirectUrlSupplier extends WWNRedirectUrlSupplier {
-
- NestTestRedirectUrlSupplier(Properties httpHeaders) {
- super(httpHeaders);
- this.cachedUrl = redirectUrl;
- }
-
- @Override
- public void resetCache() {
- // Skip resetting the URL so the test server keeps being used
- }
- }
-
- public static final ThingTypeUID THING_TYPE_TEST_BRIDGE = new ThingTypeUID(BINDING_ID, "wwn_test_account");
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_TEST_BRIDGE);
-
- private String redirectUrl;
-
- public WWNTestAccountHandler(Bridge bridge, ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory,
- String redirectUrl) {
- super(bridge, clientBuilder, eventSourceFactory);
- this.redirectUrl = redirectUrl;
- }
-
- @Override
- protected WWNRedirectUrlSupplier createRedirectUrlSupplier() throws InvalidWWNAccessTokenException {
- return new NestTestRedirectUrlSupplier(getHttpHeaders());
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.test;
-
-import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
-import static org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient.*;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.reflect.TypeToken;
-
-/**
- * The {@link WWNTestApiServlet} mocks the Nest API during tests.
- *
- * @author Wouter Born - Initial contribution
- */
-@NonNullByDefault
-public class WWNTestApiServlet extends HttpServlet {
-
- private static final long serialVersionUID = -5414910055159062745L;
-
- private static final String NEW_LINE = "\n";
-
- private static final String UPDATE_PATHS[] = { NEST_CAMERA_UPDATE_PATH, NEST_SMOKE_ALARM_UPDATE_PATH,
- NEST_STRUCTURE_UPDATE_PATH, NEST_THERMOSTAT_UPDATE_PATH };
-
- private final Logger logger = LoggerFactory.getLogger(WWNTestApiServlet.class);
-
- private static class SseEvent {
- private String name;
- private @Nullable String data;
-
- public SseEvent(String name) {
- this.name = name;
- }
-
- public SseEvent(String name, String data) {
- this.name = name;
- this.data = data;
- }
-
- public @Nullable String getData() {
- return data;
- }
-
- public String getName() {
- return name;
- }
- }
-
- private final Map<String, Map<String, String>> nestIdPropertiesMap = new ConcurrentHashMap<>();
-
- private final Map<Thread, Queue<SseEvent>> listenerQueues = new ConcurrentHashMap<>();
-
- private final ThreadLocal<PrintWriter> threadLocalWriter = new ThreadLocal<>();
-
- private final Gson gson = new GsonBuilder().create();
-
- public void closeConnections() {
- Set<Thread> threads = listenerQueues.keySet();
- listenerQueues.clear();
- threads.forEach(Thread::interrupt);
- }
-
- public void reset() {
- nestIdPropertiesMap.clear();
- }
-
- public void queueEvent(String eventName) {
- SseEvent event = new SseEvent(eventName);
- listenerQueues.forEach((thread, queue) -> queue.add(event));
- }
-
- public void queueEvent(String eventName, String data) {
- SseEvent event = new SseEvent(eventName, data);
- listenerQueues.forEach((thread, queue) -> queue.add(event));
- }
-
- @SuppressWarnings("resource")
- private void writeEvent(SseEvent event) {
- logger.debug("Writing {} event", event.getName());
-
- PrintWriter writer = threadLocalWriter.get();
-
- writer.write("event: ");
- writer.write(event.getName());
- writer.write(NEW_LINE);
-
- String eventData = event.getData();
- if (eventData != null) {
- for (String dataLine : eventData.split(NEW_LINE)) {
- writer.write("data: ");
- writer.write(dataLine);
- writer.write(NEW_LINE);
- }
- }
-
- writer.write(NEW_LINE);
- writer.flush();
- }
-
- private void writeEvent(String eventName) {
- writeEvent(new SseEvent(eventName));
- }
-
- @Override
- public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- ArrayBlockingQueue<SseEvent> queue = new ArrayBlockingQueue<>(10);
- listenerQueues.put(Thread.currentThread(), queue);
-
- response.setContentType("text/event-stream");
- response.setCharacterEncoding("UTF-8");
- response.flushBuffer();
-
- logger.debug("Opened event stream to {}:{}", request.getRemoteHost(), request.getRemotePort());
-
- PrintWriter writer = response.getWriter();
- threadLocalWriter.set(writer);
- writeEvent(OPEN);
-
- while (listenerQueues.containsKey(Thread.currentThread()) && !writer.checkError()) {
- try {
- SseEvent event = queue.poll(KEEP_ALIVE_MILLIS, TimeUnit.MILLISECONDS);
- if (event != null) {
- writeEvent(event);
- } else {
- writeEvent(KEEP_ALIVE);
- }
- } catch (InterruptedException e) {
- logger.debug("Evaluating loop conditions after interrupt");
- }
- }
-
- listenerQueues.remove(Thread.currentThread());
- threadLocalWriter.remove();
- writer.close();
-
- logger.debug("Closed event stream to {}:{}", request.getRemoteHost(), request.getRemotePort());
- }
-
- @Override
- protected void doPut(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- logger.debug("Received put request: {}", request);
-
- String uri = request.getRequestURI();
- String nestId = getNestIdFromURI(uri);
-
- if (nestId == null) {
- logger.error("Unsupported URI: {}", uri);
- response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- return;
- }
-
- InputStreamReader reader = new InputStreamReader(request.getInputStream());
- Map<String, String> propertiesUpdate = gson.fromJson(reader, new TypeToken<Map<String, String>>() {
- }.getType());
-
- Map<String, String> properties = getOrCreateProperties(nestId);
- properties.putAll(propertiesUpdate);
-
- gson.toJson(propertiesUpdate, response.getWriter());
-
- response.setStatus(HttpServletResponse.SC_OK);
- }
-
- private @Nullable String getNestIdFromURI(@Nullable String uri) {
- if (uri == null) {
- return null;
- }
- for (String updatePath : UPDATE_PATHS) {
- if (uri.startsWith(updatePath)) {
- return uri.replaceAll(updatePath, "");
- }
- }
- return null;
- }
-
- private Map<String, String> getOrCreateProperties(String nestId) {
- Map<String, String> properties = nestIdPropertiesMap.get(nestId);
- if (properties == null) {
- properties = new HashMap<>();
- nestIdPropertiesMap.put(nestId, properties);
- }
- return properties;
- }
-
- public @Nullable String getNestIdPropertyState(String nestId, String propertyName) {
- Map<String, String> properties = nestIdPropertiesMap.get(nestId);
- return properties == null ? null : properties.get(propertyName);
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.test;
-
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Map;
-
-import javax.ws.rs.client.ClientBuilder;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.nest.internal.wwn.discovery.WWNDiscoveryService;
-import org.openhab.binding.nest.internal.wwn.handler.WWNAccountHandler;
-import org.openhab.core.config.discovery.DiscoveryService;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.BaseThingHandlerFactory;
-import org.openhab.core.thing.binding.ThingHandler;
-import org.openhab.core.thing.binding.ThingHandlerFactory;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.component.ComponentContext;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Modified;
-import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.jaxrs.client.SseEventSourceFactory;
-
-/**
- * The {@link WWNTestHandlerFactory} is responsible for creating test things and thing handlers.
- *
- * @author Wouter Born - Initial contribution
- */
-@NonNullByDefault
-public class WWNTestHandlerFactory extends BaseThingHandlerFactory implements ThingHandlerFactory {
-
- public static final String REDIRECT_URL_CONFIG_PROPERTY = "redirect.url";
-
- private final ClientBuilder clientBuilder;
- private final SseEventSourceFactory eventSourceFactory;
- private final Map<ThingUID, ServiceRegistration<?>> discoveryService = new HashMap<>();
-
- private String redirectUrl = "http://localhost";
-
- @Activate
- public WWNTestHandlerFactory(@Reference ClientBuilder clientBuilder,
- @Reference SseEventSourceFactory eventSourceFactory) {
- this.clientBuilder = clientBuilder;
- this.eventSourceFactory = eventSourceFactory;
- }
-
- @Override
- public boolean supportsThingType(ThingTypeUID thingTypeUID) {
- return WWNTestAccountHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID);
- }
-
- @Activate
- public void activate(ComponentContext componentContext, Map<String, Object> config) {
- super.activate(componentContext);
- modified(config);
- }
-
- @Modified
- public void modified(Map<String, Object> config) {
- String url = (String) config.get(REDIRECT_URL_CONFIG_PROPERTY);
- if (url != null) {
- this.redirectUrl = url;
- }
- }
-
- @Override
- protected @Nullable ThingHandler createHandler(Thing thing) {
- ThingTypeUID thingTypeUID = thing.getThingTypeUID();
- if (thingTypeUID.equals(WWNTestAccountHandler.THING_TYPE_TEST_BRIDGE)) {
- WWNTestAccountHandler handler = new WWNTestAccountHandler((Bridge) thing, clientBuilder, eventSourceFactory,
- redirectUrl);
- WWNDiscoveryService service = new WWNDiscoveryService();
- service.setThingHandler(handler);
- // Register the discovery service.
- discoveryService.put(handler.getThing().getUID(),
- bundleContext.registerService(DiscoveryService.class.getName(), service, new Hashtable<>()));
-
- return handler;
- }
- return null;
- }
-
- /**
- * Removes the handler for the specific thing. This also handles disabling the discovery
- * service when the bridge is removed.
- */
- @Override
- protected void removeHandler(ThingHandler thingHandler) {
- if (thingHandler instanceof WWNAccountHandler) {
- ServiceRegistration<?> registration = discoveryService.get(thingHandler.getThing().getUID());
- if (registration != null) {
- // Unregister the discovery service.
- WWNDiscoveryService service = (WWNDiscoveryService) bundleContext
- .getService(registration.getReference());
- service.deactivate();
- registration.unregister();
- discoveryService.remove(thingHandler.getThing().getUID());
- }
- }
- super.removeHandler(thingHandler);
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.nest.internal.wwn.test;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-
-/**
- * Embedded jetty server used in the tests.
- *
- * Based on {@code TestServer} of the FS Internet Radio Binding.
- *
- * @author Velin Yordanov - Initial contribution
- * @author Wouter Born - Increase test coverage
- */
-@NonNullByDefault
-public class WWNTestServer {
- private @Nullable Server server;
- private String host;
- private int port;
- private int timeout;
- private ServletHolder servletHolder;
-
- public WWNTestServer(String host, int port, int timeout, ServletHolder servletHolder) {
- this.host = host;
- this.port = port;
- this.timeout = timeout;
- this.servletHolder = servletHolder;
- }
-
- public void startServer() throws Exception {
- Server server = new Server();
-
- ServletHandler handler = new ServletHandler();
- handler.addServletWithMapping(servletHolder, "/*");
- server.setHandler(handler);
-
- // HTTP connector
- ServerConnector http = new ServerConnector(server);
- http.setHost(host);
- http.setPort(port);
- http.setIdleTimeout(timeout);
- server.addConnector(http);
-
- server.start();
-
- this.server = server;
- }
-
- public void stopServer() throws Exception {
- Server server = this.server;
- if (server == null) {
- return;
- }
-
- server.stop();
- this.server = null;
- }
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="nest"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
- xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
- <bridge-type id="test_account">
- <label>Test Account</label>
- <description>An account for testing the Nest binding</description>
- <config-description-ref uri="thing-type:nest:account"/>
- </bridge-type>
-</thing:thing-descriptions>
+++ /dev/null
-{
- "access_token": "access_token",
- "expires_in": 315360000
-}
+++ /dev/null
-{
- "app_url": "https://camera_app_url",
- "device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
- "is_audio_input_enabled": true,
- "is_online": true,
- "is_public_share_enabled": false,
- "is_streaming": false,
- "is_video_history_enabled": false,
- "last_event": {
- "activity_zone_ids": [
- "id1",
- "id2"
- ],
- "animated_image_url": "https://last_event_animated_image_url",
- "app_url": "https://last_event_app_url",
- "end_time": "2017-01-22T07:40:38.680Z",
- "has_motion": true,
- "has_person": false,
- "has_sound": false,
- "image_url": "https://last_event_image_url",
- "start_time": "2017-01-22T07:40:19.020Z",
- "urls_expire_time": "2017-02-05T07:40:19.020Z",
- "web_url": "https://last_event_web_url"
- },
- "last_is_online_change": "2017-01-22T08:19:20.000Z",
- "name": "Upstairs",
- "name_long": "Upstairs Camera",
- "snapshot_url": "https://camera_snapshot_url",
- "software_version": "205-600052",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "web_url": "https://camera_web_url",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
-}
+++ /dev/null
-{
- "error": "blocked",
- "type": "https://developer.nest.com/documentation/cloud/error-messages#blocked",
- "message": "blocked",
- "instance": "bb514046-edc9-4bca-8239-f7a3cfb0925a"
-}
+++ /dev/null
-{
- "battery_health": "ok",
- "co_alarm_state": "ok",
- "device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV",
- "is_manual_test_active": false,
- "is_online": true,
- "last_connection": "2017-02-02T20:53:05.338Z",
- "locale": "en-US",
- "name": "Downstairs",
- "name_long": "Downstairs Nest Protect",
- "smoke_alarm_state": "ok",
- "software_version": "3.1rc9",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "ui_color_state": "green",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
- "where_name": "Downstairs"
-}
+++ /dev/null
-{
- "smoke_co_alarms": [
- "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
- "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
- "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
- "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
- ],
- "name": "Home",
- "country_code": "US",
- "postal_code": "98056",
- "time_zone": "America/Los_Angeles",
- "away": "home",
- "thermostats": [
- "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
- ],
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "rhr_enrollment": false,
- "co_alarm_state": "ok",
- "smoke_alarm_state": "ok",
- "eta_begin": "2017-02-02T03:10:08.000Z",
- "wwn_security_state": "ok",
- "wheres": {
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg",
- "name": "Basement"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ",
- "name": "Bedroom"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw",
- "name": "Den"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g",
- "name": "Dining Room"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
- "name": "Downstairs"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg",
- "name": "Entryway"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA",
- "name": "Family Room"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw",
- "name": "Hallway"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA",
- "name": "Kids Room"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA",
- "name": "Kitchen"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
- "name": "Living Room"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw",
- "name": "Master Bedroom"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q",
- "name": "Office"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA",
- "name": "Upstairs"
- },
- "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ": {
- "where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ",
- "name": "Downstairs Kitchen"
- },
- "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ": {
- "where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ",
- "name": "Garage"
- },
- "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ": {
- "where_id": "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ",
- "name": "Frog"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA",
- "name": "Backyard"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA",
- "name": "Driveway"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g",
- "name": "Front Yard"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ": {
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ",
- "name": "Outside"
- }
- },
- "cameras": [
- "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
- "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
- ]
-}
+++ /dev/null
-{
- "ambient_temperature_c": 19.0,
- "ambient_temperature_f": 66,
- "away_temperature_high_c": 24.0,
- "away_temperature_high_f": 76,
- "away_temperature_low_c": 12.5,
- "away_temperature_low_f": 55,
- "can_cool": false,
- "can_heat": true,
- "device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV",
- "eco_temperature_high_c": 24.0,
- "eco_temperature_high_f": 76,
- "eco_temperature_low_c": 12.5,
- "eco_temperature_low_f": 55,
- "fan_timer_active": false,
- "fan_timer_duration": 15,
- "fan_timer_timeout": "1970-01-01T00:00:00.000Z",
- "has_fan": true,
- "has_leaf": true,
- "humidity": 25,
- "hvac_mode": "heat",
- "hvac_state": "off",
- "is_locked": false,
- "is_online": true,
- "is_using_emergency_heat": false,
- "label": "Living Room",
- "last_connection": "2017-02-02T21:00:06.000Z",
- "locale": "en-GB",
- "locked_temp_max_c": 22.0,
- "locked_temp_max_f": 72,
- "locked_temp_min_c": 20.0,
- "locked_temp_min_f": 68,
- "name": "Living Room (Living Room)",
- "name_long": "Living Room Thermostat (Living Room)",
- "previous_hvac_mode": "",
- "software_version": "5.6-7",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "sunlight_correction_active": false,
- "sunlight_correction_enabled": true,
- "target_temperature_c": 15.5,
- "target_temperature_f": 60,
- "target_temperature_high_c": 24.0,
- "target_temperature_high_f": 75,
- "target_temperature_low_c": 20.0,
- "target_temperature_low_f": 68,
- "temperature_scale": "C",
- "time_to_target": "~0",
- "time_to_target_training": "ready",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
- "where_name": "Living Room"
-}
+++ /dev/null
-{
- "devices": {
- "cameras": {
- "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ": {
- "app_url": "https://camera_app_url",
- "device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
- "is_audio_input_enabled": true,
- "is_online": false,
- "is_public_share_enabled": false,
- "is_streaming": false,
- "is_video_history_enabled": false,
- "last_event": {
- "activity_zone_ids": [
- "id1",
- "id2"
- ],
- "animated_image_url": "https://last_event_animated_image_url",
- "app_url": "https://last_event_app_url",
- "end_time": "2017-01-22T07:40:38.680Z",
- "has_motion": true,
- "has_person": false,
- "has_sound": false,
- "image_url": "https://last_event_image_url",
- "start_time": "2017-01-22T07:40:19.020Z",
- "urls_expire_time": "2017-02-05T07:40:19.020Z",
- "web_url": "https://last_event_web_url"
- },
- "last_is_online_change": "2017-01-22T08:19:20.000Z",
- "name": "Upstairs",
- "name_long": "Upstairs Camera",
- "snapshot_url": "https://camera_snapshot_url",
- "software_version": "205-600052",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "web_url": "https://camera_web_url",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
- },
- "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ": {
- "app_url": "nestmobile://cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
- "device_id": "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ",
- "is_audio_input_enabled": true,
- "is_online": false,
- "is_public_share_enabled": false,
- "is_streaming": false,
- "is_video_history_enabled": false,
- "last_event": {
- "end_time": "2016-11-20T07:02:46.860Z",
- "has_motion": true,
- "has_person": false,
- "has_sound": false,
- "start_time": "2016-11-20T07:02:27.260Z"
- },
- "last_is_online_change": "2016-11-20T07:03:42.000Z",
- "name": "Garage",
- "name_long": "Garage Camera",
- "snapshot_url": "https://www.dropcam.com/api/wwn.get_snapshot/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
- "software_version": "205-600052",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "web_url": "https://home.nest.com/cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
- "where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
- }
- },
- "smoke_co_alarms": {
- "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV": {
- "battery_health": "ok",
- "co_alarm_state": "ok",
- "device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV",
- "is_manual_test_active": false,
- "is_online": true,
- "last_connection": "2017-02-02T20:53:05.338Z",
- "locale": "en-US",
- "name": "Downstairs",
- "name_long": "Downstairs Nest Protect",
- "smoke_alarm_state": "ok",
- "software_version": "3.1rc9",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "ui_color_state": "green",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
- "where_name": "Downstairs"
- },
- "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV": {
- "battery_health": "ok",
- "co_alarm_state": "ok",
- "device_id": "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
- "is_manual_test_active": false,
- "is_online": true,
- "last_connection": "2017-02-02T20:35:50.051Z",
- "last_manual_test_time": "1970-01-01T00:00:00.000Z",
- "locale": "en-US",
- "name": "Upstairs",
- "name_long": "Upstairs Nest Protect",
- "smoke_alarm_state": "ok",
- "software_version": "3.1rc9",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "ui_color_state": "green",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA",
- "where_name": "Upstairs"
- },
- "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV": {
- "battery_health": "ok",
- "co_alarm_state": "ok",
- "device_id": "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
- "is_manual_test_active": false,
- "is_online": true,
- "last_connection": "2017-02-02T11:04:18.804Z",
- "last_manual_test_time": "1970-01-01T00:00:00.000Z",
- "locale": "en-US",
- "name": "Downstairs Kitchen",
- "name_long": "Downstairs Kitchen Nest Protect",
- "smoke_alarm_state": "ok",
- "software_version": "3.1rc9",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "ui_color_state": "green",
- "where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ",
- "where_name": "Downstairs Kitchen"
- },
- "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV": {
- "battery_health": "ok",
- "co_alarm_state": "ok",
- "device_id": "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
- "is_manual_test_active": false,
- "is_online": true,
- "last_connection": "2017-02-02T13:30:34.187Z",
- "last_manual_test_time": "1970-01-01T00:00:00.000Z",
- "locale": "en-US",
- "name": "Living Room",
- "name_long": "Living Room Nest Protect",
- "smoke_alarm_state": "ok",
- "software_version": "3.1rc9",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "ui_color_state": "green",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
- "where_name": "Living Room"
- }
- },
- "thermostats": {
- "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV": {
- "ambient_temperature_c": 19.0,
- "ambient_temperature_f": 66,
- "away_temperature_high_c": 24.0,
- "away_temperature_high_f": 76,
- "away_temperature_low_c": 12.5,
- "away_temperature_low_f": 55,
- "can_cool": false,
- "can_heat": true,
- "device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV",
- "eco_temperature_high_c": 24.0,
- "eco_temperature_high_f": 76,
- "eco_temperature_low_c": 12.5,
- "eco_temperature_low_f": 55,
- "fan_timer_active": false,
- "fan_timer_duration": 15,
- "fan_timer_timeout": "1970-01-01T00:00:00.000Z",
- "has_fan": true,
- "has_leaf": true,
- "humidity": 25,
- "hvac_mode": "heat",
- "hvac_state": "off",
- "is_locked": false,
- "is_online": true,
- "is_using_emergency_heat": false,
- "label": "Living Room",
- "last_connection": "2017-02-02T21:00:06.000Z",
- "locale": "en-GB",
- "locked_temp_max_c": 22.0,
- "locked_temp_max_f": 72,
- "locked_temp_min_c": 20.0,
- "locked_temp_min_f": 68,
- "name": "Living Room (Living Room)",
- "name_long": "Living Room Thermostat (Living Room)",
- "previous_hvac_mode": "",
- "software_version": "5.6-7",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "sunlight_correction_active": false,
- "sunlight_correction_enabled": true,
- "target_temperature_c": 15.5,
- "target_temperature_f": 60,
- "target_temperature_high_c": 24.0,
- "target_temperature_high_f": 75,
- "target_temperature_low_c": 20.0,
- "target_temperature_low_f": 68,
- "temperature_scale": "C",
- "time_to_target": "~0",
- "time_to_target_training": "ready",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
- "where_name": "Living Room"
- }
- }
- },
- "metadata": {
- "access_token": "c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
- "client_version": 1
- },
- "structures": {
- "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A": {
- "away": "home",
- "cameras": [
- "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
- "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
- ],
- "co_alarm_state": "ok",
- "country_code": "US",
- "eta_begin": "2017-02-02T03:10:08.000Z",
- "name": "Home",
- "postal_code": "98056",
- "rhr_enrollment": false,
- "smoke_alarm_state": "ok",
- "smoke_co_alarms": [
- "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
- "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
- "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
- "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
- ],
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "thermostats": [
- "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
- ],
- "time_zone": "America/Los_Angeles",
- "wheres": {
- "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ": {
- "name": "Downstairs Kitchen",
- "where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ"
- },
- "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ": {
- "name": "Frog",
- "where_id": "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ"
- },
- "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ": {
- "name": "Garage",
- "where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA": {
- "name": "Family Room",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA": {
- "name": "Kitchen",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw": {
- "name": "Hallway",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg": {
- "name": "Basement",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA": {
- "name": "Kids Room",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw": {
- "name": "Master Bedroom",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg": {
- "name": "Downstairs",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA": {
- "name": "Driveway",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw": {
- "name": "Den",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ": {
- "name": "Bedroom",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg": {
- "name": "Entryway",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA": {
- "name": "Upstairs",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw": {
- "name": "Living Room",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ": {
- "name": "Outside",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g": {
- "name": "Dining Room",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA": {
- "name": "Backyard",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q": {
- "name": "Office",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g": {
- "name": "Front Yard",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g"
- }
- },
- "wwn_security_state": "ok"
- }
- }
-}
+++ /dev/null
-{
- "path": "/",
- "data": {
- }
-}
+++ /dev/null
-{
- "path": "/",
- "data": {
- "devices": {
- "cameras": {
- "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ": {
- "device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ"
- },
- "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ": {
- "device_id": "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
- }
- },
- "smoke_co_alarms": {
- "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV": {
- "device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
- },
- "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV": {
- "device_id": "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV"
- },
- "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV": {
- "device_id": "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV"
- },
- "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV": {
- "device_id": "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV"
- }
- },
- "thermostats": {
- "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV": {
- "device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
- },
- "OTQoylk2h5Ld3cfpm3esR0qx-iQr8PMV": {
- "device_id": "OTQoylk2h5Ld3cfpm3esR0qx-iQr8PMV"
- }
- }
- },
- "structures": {
- "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A": {
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A"
- },
- "SylKI7puaWd56ILAcJ46LzmtdZc3L4wGzScs8yLc5zccJofBIW9KTJ": {
- "structure_id": "SylKI7puaWd56ILAcJ46LzmtdZc3L4wGzScs8yLc5zccJofBIW9KTJ"
- }
- }
- }
-}
+++ /dev/null
-{
- "path": "/",
- "data": {
- "devices": {
- "cameras": {
- "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ": {
- "app_url": "https://camera_app_url",
- "device_id": "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
- "is_audio_input_enabled": true,
- "is_online": true,
- "is_public_share_enabled": false,
- "is_streaming": false,
- "is_video_history_enabled": false,
- "last_event": {
- "activity_zone_ids": [
- "id1",
- "id2"
- ],
- "animated_image_url": "https://last_event_animated_image_url",
- "app_url": "https://last_event_app_url",
- "end_time": "2017-01-22T07:40:38.680Z",
- "has_motion": true,
- "has_person": false,
- "has_sound": false,
- "image_url": "https://last_event_image_url",
- "start_time": "2017-01-22T07:40:19.020Z",
- "urls_expire_time": "2017-02-05T07:40:19.020Z",
- "web_url": "https://last_event_web_url"
- },
- "last_is_online_change": "2017-01-22T08:19:20.000Z",
- "name": "Upstairs",
- "name_long": "Upstairs Camera",
- "public_share_url": "https://camera_public_share_url",
- "snapshot_url": "https://camera_snapshot_url",
- "software_version": "205-600052",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "web_url": "https://camera_web_url",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
- },
- "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ": {
- "app_url": "nestmobile://cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
- "device_id": "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ",
- "is_audio_input_enabled": true,
- "is_online": false,
- "is_public_share_enabled": false,
- "is_streaming": false,
- "is_video_history_enabled": false,
- "last_event": {
- "end_time": "2016-11-20T07:02:46.860Z",
- "has_motion": true,
- "has_person": false,
- "has_sound": false,
- "start_time": "2016-11-20T07:02:27.260Z"
- },
- "last_is_online_change": "2016-11-20T07:03:42.000Z",
- "name": "Garage",
- "name_long": "Garage Camera",
- "snapshot_url": "https://www.dropcam.com/api/wwn.get_snapshot/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
- "software_version": "205-600052",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "web_url": "https://home.nest.com/cameras/CjZWRzdDN0JVNlpmOE9qRWZpem1CQ1Zud251S0hTbk9CSUhnYlFLYTU3eEtKenJ2b2tLX0R6RlESFm9wNVB2NW93NmJ6cUdvMkZQSGUxdEEaNld0Mkl5b2tIR0tKX2FpUVd1SkRnQjc2ejhSWFl3SFFxWXFrSWx2QlpxN1gyeWNqdmRZVjdGQQ?auth=c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
- "where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
- }
- },
- "smoke_co_alarms": {
- "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV": {
- "battery_health": "ok",
- "co_alarm_state": "ok",
- "device_id": "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV",
- "is_manual_test_active": false,
- "is_online": true,
- "last_connection": "2017-02-02T20:53:05.338Z",
- "last_manual_test_time": "2016-10-31T23:59:59.000Z",
- "locale": "en-US",
- "name": "Downstairs",
- "name_long": "Downstairs Nest Protect",
- "smoke_alarm_state": "ok",
- "software_version": "3.1rc9",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "ui_color_state": "green",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg",
- "where_name": "Downstairs"
- },
- "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV": {
- "battery_health": "ok",
- "co_alarm_state": "ok",
- "device_id": "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
- "is_manual_test_active": false,
- "is_online": true,
- "last_connection": "2017-02-02T20:35:50.051Z",
- "last_manual_test_time": "1970-01-01T00:00:00.000Z",
- "locale": "en-US",
- "name": "Upstairs",
- "name_long": "Upstairs Nest Protect",
- "smoke_alarm_state": "ok",
- "software_version": "3.1rc9",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "ui_color_state": "green",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA",
- "where_name": "Upstairs"
- },
- "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV": {
- "battery_health": "ok",
- "co_alarm_state": "ok",
- "device_id": "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
- "is_manual_test_active": false,
- "is_online": true,
- "last_connection": "2017-02-02T11:04:18.804Z",
- "last_manual_test_time": "1970-01-01T00:00:00.000Z",
- "locale": "en-US",
- "name": "Downstairs Kitchen",
- "name_long": "Downstairs Kitchen Nest Protect",
- "smoke_alarm_state": "ok",
- "software_version": "3.1rc9",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "ui_color_state": "green",
- "where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ",
- "where_name": "Downstairs Kitchen"
- },
- "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV": {
- "battery_health": "ok",
- "co_alarm_state": "ok",
- "device_id": "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
- "is_manual_test_active": false,
- "is_online": true,
- "last_connection": "2017-02-02T13:30:34.187Z",
- "last_manual_test_time": "1970-01-01T00:00:00.000Z",
- "locale": "en-US",
- "name": "Living Room",
- "name_long": "Living Room Nest Protect",
- "smoke_alarm_state": "ok",
- "software_version": "3.1rc9",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "ui_color_state": "green",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
- "where_name": "Living Room"
- }
- },
- "thermostats": {
- "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV": {
- "ambient_temperature_c": 19.0,
- "ambient_temperature_f": 66,
- "away_temperature_high_c": 24.0,
- "away_temperature_high_f": 76,
- "away_temperature_low_c": 12.5,
- "away_temperature_low_f": 55,
- "can_cool": false,
- "can_heat": true,
- "device_id": "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV",
- "eco_temperature_high_c": 24.0,
- "eco_temperature_high_f": 76,
- "eco_temperature_low_c": 12.5,
- "eco_temperature_low_f": 55,
- "fan_timer_active": false,
- "fan_timer_duration": 15,
- "fan_timer_timeout": "1970-01-01T00:00:00.000Z",
- "has_fan": true,
- "has_leaf": true,
- "humidity": 25,
- "hvac_mode": "heat",
- "hvac_state": "off",
- "is_locked": false,
- "is_online": true,
- "is_using_emergency_heat": false,
- "label": "Living Room",
- "last_connection": "2017-02-02T21:00:06.000Z",
- "locale": "en-GB",
- "locked_temp_max_c": 22.0,
- "locked_temp_max_f": 72,
- "locked_temp_min_c": 20.0,
- "locked_temp_min_f": 68,
- "name": "Living Room (Living Room)",
- "name_long": "Living Room Thermostat (Living Room)",
- "previous_hvac_mode": "",
- "software_version": "5.6-7",
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "sunlight_correction_active": false,
- "sunlight_correction_enabled": true,
- "target_temperature_c": 15.5,
- "target_temperature_f": 60,
- "target_temperature_high_c": 24.0,
- "target_temperature_high_f": 75,
- "target_temperature_low_c": 20.0,
- "target_temperature_low_f": 68,
- "temperature_scale": "C",
- "time_to_target": "~0",
- "time_to_target_training": "ready",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw",
- "where_name": "Living Room"
- }
- }
- },
- "metadata": {
- "access_token": "c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc",
- "client_version": 1
- },
- "structures": {
- "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A": {
- "away": "home",
- "cameras": [
- "_LK8j9rRXwCKEBOtDo7JskNxzWfHBOIm3CLouCT3FQZzrvokK_DzFQ",
- "VG7C7BU6Zf8OjEfizmBCVnwnuKHSnOBIHgbQKa57xKJzrvokK_DzFQ"
- ],
- "co_alarm_state": "ok",
- "country_code": "US",
- "eta_begin": "2017-02-02T03:10:08.000Z",
- "name": "Home",
- "peak_period_end_time": "2017-07-01T01:03:08.400Z",
- "peak_period_start_time": "2017-06-01T13:31:10.870Z",
- "postal_code": "98056",
- "rhr_enrollment": false,
- "smoke_alarm_state": "ok",
- "smoke_co_alarms": [
- "p1b1oySOcs-OJHIgmgeMkHOu-iQr8PMV",
- "p1b1oySOcs8Qu7IAJVrQ7XOu-iQr8PMV",
- "p1b1oySOcs8W9WwaNu80oXOu-iQr8PMV",
- "p1b1oySOcs_sbi4iczruW3Ou-iQr8PMV"
- ],
- "structure_id": "ysCnsCaq1pQwKUPP9H4AqE943C1XtLin3x6uCVN5Qh09IDyTg7Ey5A",
- "thermostats": [
- "G1jouHN5yl6mXFaQw5iGwXOu-iQr8PMV"
- ],
- "time_zone": "America/Los_Angeles",
- "wheres": {
- "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ": {
- "name": "Downstairs Kitchen",
- "where_id": "6UAWzz8czKpFrH6EK3AcjDiTjbRgts8x5MJxEnn1yKKQpYTBO7n2UQ"
- },
- "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ": {
- "name": "Frog",
- "where_id": "8tH6YiXUAQDZFLD6AgMmQ14Sc5wTG0NxKfabPY0XKrqc47t3uSDZvQ"
- },
- "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ": {
- "name": "Garage",
- "where_id": "qpWvTu89Knhn6GRFM-VtGoE4KYwbzbJg9INR6WyPfhW1EJ04GRyYbQ"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA": {
- "name": "Family Room",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIAYVvcpN1cOA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA": {
- "name": "Kitchen",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB2f05cPKRBA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw": {
- "name": "Hallway",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIB7GULj0y7Rw"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg": {
- "name": "Basement",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIYpqdaXnYjUg"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA": {
- "name": "Kids Room",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIbTUmML4Q6xA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw": {
- "name": "Master Bedroom",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIebdVzhA62Iw"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg": {
- "name": "Downstairs",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsIm5E0NfJPeeg"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA": {
- "name": "Driveway",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJv12iEHQ0hxA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw": {
- "name": "Den",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsJyRQEOtmKqkw"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ": {
- "name": "Bedroom",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK-nCnEjccnMQ"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg": {
- "name": "Entryway",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsK2kdsXRP3IFg"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA": {
- "name": "Upstairs",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKCxvyZfxNpKA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw": {
- "name": "Living Room",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKQrCrjN0yXiw"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ": {
- "name": "Outside",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKR8TWb9hTptQ"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g": {
- "name": "Dining Room",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKZphUIYeW39g"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA": {
- "name": "Backyard",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKfexoqPTcUVA"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q": {
- "name": "Office",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsKtUyRb3je64Q"
- },
- "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g": {
- "name": "Front Yard",
- "where_id": "z8fK075vJJPPWnXxLx1m3GskRSZQ64iQydB59k-UPsLRu9lIioI47g"
- }
- },
- "wwn_security_state": "ok"
- }
- }
- }
-}
<module>org.openhab.binding.mqtt.homeassistant.tests</module>
<module>org.openhab.binding.mqtt.homie.tests</module>
<module>org.openhab.binding.mqtt.ruuvigateway.tests</module>
- <module>org.openhab.binding.nest.tests</module>
<module>org.openhab.binding.ntp.tests</module>
<module>org.openhab.binding.systeminfo.tests</module>
<module>org.openhab.binding.tradfri.tests</module>