[hydrawise] Migrate to new GraphQL based API (#10947)
* [hydrawise] Migrated to new GraphQL based API Fixes #7261 Signed-off-by: Dan Cunningham <dan@digitaldan.com> * Addressed PR comments. Signed-off-by: Dan Cunningham <dan@digitaldan.com> * Address PR review comments. Signed-off-by: Dan Cunningham <dan@digitaldan.com>
This commit is contained in:
parent
f25cc8d14a
commit
e465155d84
@ -6,20 +6,32 @@ The Hydrawise binding allows monitoring and control of [Hunter Industries's](htt
|
|||||||
|
|
||||||
## Supported Things
|
## Supported Things
|
||||||
|
|
||||||
### Cloud Thing
|
|
||||||
|
### Account Bridge Thing
|
||||||
|
|
||||||
The Cloud Thing type is the primary way most users will control and monitor their irrigation system.
|
The Account Bridge Thing type represents the user's account on the Hydrawise cloud service. The bridge can have one or more child [Controllers](#Controller-Thing) linked.
|
||||||
|
|
||||||
|
An account must be manually added and configured.
|
||||||
|
|
||||||
|
### Controller Thing
|
||||||
|
|
||||||
|
Controller Things are automatically discovered once an [Account Bridge](#Account-Bridge-Thing) has be properly configured.
|
||||||
|
|
||||||
|
The Controller Thing type is the primary way most users will control and monitor their irrigation system.
|
||||||
This allows full control over zones, sensors and weather forecasts.
|
This allows full control over zones, sensors and weather forecasts.
|
||||||
Changes made through this Thing type will be reflected in the Hydrawise mobile and web applications as well as in their reporting modules.
|
Changes made through this Thing type will be reflected in the Hydrawise mobile and web applications as well as in their reporting modules.
|
||||||
|
|
||||||
#### Cloud Thing Supported Channel Groups
|
Controller Things require a parent [Account Bridge](#Account-Bridge-Thing)
|
||||||
|
|
||||||
| channel group ID |
|
#### Controller Thing Supported Channel Groups
|
||||||
|---------------------------------------|
|
|
||||||
| [Zones](#Zone-Channel-Group) |
|
| channel group ID |
|
||||||
| [All Zones](#All-Zones-Channel-Group) |
|
|-----------------------------------------------|
|
||||||
| [Sensor](#Sensor-Channel-Group) |
|
| [Controller](#Cloud-Controller-Channel-Group) |
|
||||||
| [Forecast](#Sensor-Channel-Group) |
|
| [Zones](#Zone-Channel-Group) |
|
||||||
|
| [All Zones](#All-Zones-Channel-Group) |
|
||||||
|
| [Sensor](#Sensor-Channel-Group) |
|
||||||
|
| [Forecast](#Sensor-Channel-Group) |
|
||||||
|
|
||||||
### Local Thing
|
### Local Thing
|
||||||
|
|
||||||
@ -27,6 +39,8 @@ The Local Thing type uses an undocumented API that allows direct HTTP access to
|
|||||||
This provides a subset of features compared to the Cloud Thing type limited to basic zone control.
|
This provides a subset of features compared to the Cloud Thing type limited to basic zone control.
|
||||||
Controlling zones through the local API will not be reported back to the cloud service or the Hydrawise mobile/web applications, and reporting functionality will not reflect the locally controlled state.
|
Controlling zones through the local API will not be reported back to the cloud service or the Hydrawise mobile/web applications, and reporting functionality will not reflect the locally controlled state.
|
||||||
|
|
||||||
|
Local control may not be available on later Hydrawise controller firmware versions.
|
||||||
|
|
||||||
Use Cases
|
Use Cases
|
||||||
|
|
||||||
* The Local thing can be useful when testing zones, as there is no delay when starting/stopping zones as compared to the cloud API which can take anywhere between 5-15 seconds.
|
* The Local thing can be useful when testing zones, as there is no delay when starting/stopping zones as compared to the cloud API which can take anywhere between 5-15 seconds.
|
||||||
@ -41,28 +55,29 @@ Use Cases
|
|||||||
|
|
||||||
## Thing Configuration
|
## Thing Configuration
|
||||||
|
|
||||||
### Cloud Thing
|
### Account Thing
|
||||||
|
|
||||||
| Configuration Name | type | required | Comments |
|
| Configuration Name | type | required | Comments |
|
||||||
|--------------------|---------|----------|------------------------------------------------------------------------------------|
|
|--------------------|---------|----------|--------------------------------------------------------------------------------------------------------------------------|
|
||||||
| apiKey | String | True | |
|
| userName | String | False | The Hydrawise account user name |
|
||||||
| refresh | Integer | True | Defaults to a 30 seconds polling rate |
|
| password | String | False | The Hydrawise account password |
|
||||||
| controllerId | Integer | False | Optional id of the controller if you have more then one registered to your account |
|
| savePassword | Boolean | False | By default the password will be not be persisted after the first login attempt unless this is true, defaults to false |
|
||||||
|
| refresh | Integer | False | Defaults to a 60 second polling rate, more frequent polling may cause the service to deny requests |
|
||||||
|
| refreshToken | Boolean | False | A oAuth refresh token, this will be automatically configured after the first login and updated as the token is refreshed |
|
||||||
|
|
||||||
To obtain your API key, log into your [Hydrawsie Account](https://app.hydrawise.com/config/login) and click on your account icon, then account details:
|
### Controller Thing
|
||||||
|
|
||||||

|
| Configuration Name | type | required | Comments |
|
||||||
|
|--------------------|---------|----------|----------------------|
|
||||||
|
| controllerId | Integer | True | ID of the controller |
|
||||||
|
|
||||||
Then copy the API key shown here:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Local Thing
|
### Local Thing
|
||||||
|
|
||||||
| Configuration Name | type | required | Comments |
|
| Configuration Name | type | required | Comments |
|
||||||
|--------------------|---------|----------|-----------------------------------------------------------------------------------------------------------------|
|
|--------------------|---------|----------|-----------------------------------------------------------------------------------------------------------------|
|
||||||
| host | String | True | IP or host name of the controller on your network |
|
| host | String | True | IP or host name of the controller on your network |
|
||||||
| username | String | True | User name (usually admin) set on the touch panel of the controller |
|
| username | String | True | User name (usually admin) set on the touch panel of the controller |
|
||||||
| password | String | True | Password set on the touch panel of the controller. This can be found under the setting menu on the controller. |
|
| password | String | True | Password set on the touch panel of the controller. This can be found under the setting menu on the controller. |
|
||||||
| refresh | Integer | True | Defaults to a 30 seconds polling rate |
|
| refresh | Integer | True | Defaults to a 30 seconds polling rate |
|
||||||
|
|
||||||
@ -70,6 +85,12 @@ Then copy the API key shown here:
|
|||||||
|
|
||||||
### Channel Groups
|
### Channel Groups
|
||||||
|
|
||||||
|
#### System Channel Group
|
||||||
|
|
||||||
|
| channel group ID | Description |
|
||||||
|
|------------------|---------------------------------|
|
||||||
|
| system | System status of the controller |
|
||||||
|
|
||||||
#### Zone Channel Group
|
#### Zone Channel Group
|
||||||
|
|
||||||
Up to 36 total zones are supported per Local or Cloud thing
|
Up to 36 total zones are supported per Local or Cloud thing
|
||||||
@ -94,14 +115,13 @@ Up to 4 total sensors are supported per Cloud Thing
|
|||||||
|
|
||||||
#### Forecast Channel Group
|
#### Forecast Channel Group
|
||||||
|
|
||||||
Up to 4 total weather forecasts are supported per Cloud Thing
|
Up to 3 total weather forecasts are supported per Cloud Thing
|
||||||
|
|
||||||
| channel group ID | Description |
|
| channel group ID | Description |
|
||||||
|------------------|-----------------|
|
|------------------|-----------------|
|
||||||
| forecast1 | Todays Forecast |
|
| forecast1 | Todays Forecast |
|
||||||
| forecast2 | Day 2 Forecast |
|
| forecast2 | Day 2 Forecast |
|
||||||
| forecast3 | Day 3 Forecast |
|
| forecast3 | Day 3 Forecast |
|
||||||
| forecast4 | Day 4 Forecast |
|
|
||||||
|
|
||||||
#### All Zones Channel Group
|
#### All Zones Channel Group
|
||||||
|
|
||||||
@ -114,58 +134,81 @@ A single all zone group are supported per Cloud or Local Thing
|
|||||||
|
|
||||||
### Channels
|
### Channels
|
||||||
|
|
||||||
| channel ID | type | Groups | description | Read Write |
|
Channels uses across zones, sensors and forecasts
|
||||||
|-----------------|--------------------|----------------|---------------------------------------------|------------|
|
| channel ID | type | Groups | description | Read Write |
|
||||||
| name | String | zone, sensor | Descriptive name | R |
|
|----------------------------|--------------------|----------------|-----------------------------------------------|------------|
|
||||||
| icon | String | zone | Icon URL | R |
|
| name | String | zone, sensor | Descriptive name | R |
|
||||||
| time | Number | zone | Zone start time in seconds | R |
|
| icon | String | zone | Icon URL | R |
|
||||||
| type | Number | zone | Zone type | R |
|
| type | Number | zone | Zone type | R |
|
||||||
| runcustom | Number | zone, allzones | Run zone for custom number of seconds | W |
|
| run | Switch | zone, allzones | Run/Start zone | RW |
|
||||||
| run | Switch | zone, allzones | Run/Start zone | RW |
|
| runcustom | Number:Time | zone, allzones | Run zone for custom length | W |
|
||||||
| nextrun | DateTime | zone | Next date and time this zone will run | R |
|
| suspend | Switch | zone, allzones | Suspend zone | RW |
|
||||||
| timeleft | Number | zone | Amount of seconds left for the running zone | R |
|
| suspenduntil | DateTime | zone, allzones | Suspend zone unitl specified date | RW |
|
||||||
| input | Number | sensor | Sensor input type | R |
|
| nextrun | DateTime | zone | Next date and time this zone will run | R |
|
||||||
| mode | Number | sensor | Sensor mode | R |
|
| timeleft | Number:Time | zone | Amount of time left for the running zone | R |
|
||||||
| timer | Number | sensor | Sensor timer | R |
|
| input | Number | sensor | Sensor input type | R |
|
||||||
| offtimer | Number | sensor | Sensor off time | R |
|
| timer | Number | sensor | Sensor timer | R |
|
||||||
| offlevel | Number | sensor | Sensor off level | R |
|
| offtimer | Number:Time | sensor | Sensor off timer | R |
|
||||||
| active | Switch | sensor | Is sensor active / triggered | R |
|
| offlevel | Number | sensor | Sensor off level | R |
|
||||||
| temperaturehigh | Number:Temperature | forecast | Daily high temperature | R |
|
| active | Switch | sensor | Is sensor active / triggered | R |
|
||||||
| temperaturelow | Number:Temperature | forecast | Daily low temperature | R |
|
| temperaturehigh | Number:Temperature | forecast | Daily high temperature | R |
|
||||||
| conditions | String | forecast | Daily conditions description | R |
|
| temperaturelow | Number:Temperature | forecast | Daily low temperature | R |
|
||||||
| day | String | forecast | Day of week of forecast (Mon-Sun) | R |
|
| conditions | String | forecast | Daily conditions description | R |
|
||||||
| humidity | Number | forecast | Daily humidity percentage | R |
|
| day | DateTime | forecast | Day of week of forecast (Mon-Sun) | R |
|
||||||
| wind | Number:Speed | forecast | Daily wind speed | R |
|
| humidity | Number | forecast | Daily humidity percentage | R |
|
||||||
|
| wind | Number:Speed | forecast | Daily wind speed | R |
|
||||||
|
| evapotranspiration | Number | forecast | Daily evapotranspiration amount | R |
|
||||||
|
| precipitation | Number | forecast | Daily precipitation amount | R |
|
||||||
|
| probabilityofprecipitation | Number | forecast | Daily probability of precipitation percentage | R |
|
||||||
|
|
||||||
|
|
||||||
## Full Example
|
## Full Example
|
||||||
|
|
||||||
```
|
```
|
||||||
Group SprinklerZones
|
Group Sprinkler "Sprinkler"
|
||||||
|
Group SprinklerController "Controller" (Sprinkler)
|
||||||
|
Group SprinklerZones "Zones" (Sprinkler)
|
||||||
|
Group SprinklerSensors "Sensors" (Sprinkler)
|
||||||
|
Group SprinkerForecast "Forecast" (Sprinkler)
|
||||||
|
|
||||||
|
String SprinkerControllerStatus "Status [%s]" (SprinklerController) {channel="hydrawise:controller:myaccount:123456:controller#status"}
|
||||||
|
Number SprinkerControllerLastContact "Last Contact [%d]" (SprinklerController) {channel="hydrawise:controller:myaccount:123456:controller#lastContact"}
|
||||||
|
|
||||||
|
Switch SprinklerSensor1 "Sprinler Sensor" (SprinklerSensors) {channel="hydrawise:controller:myaccount:123456:sensor1#active"}
|
||||||
|
|
||||||
|
Group SprinkerForecastDay1 "Todays Forecast" (SprinkerForecast)
|
||||||
|
Number:Temperature SprinkerForecastDay1HiTemp "High Temp [%d]" (SprinkerForecastDay1) {channel="hydrawise:controller:myaccount:123456:forecast1#temperaturehigh"}
|
||||||
|
Number:Temperature SprinkerForecastDay1LowTemp "Low Temp [%d]" (SprinkerForecastDay1) {channel="hydrawise:controller:myaccount:123456:forecast1#temperaturelow"}
|
||||||
|
String SprinkerForecastDay1Conditions "Conditions [%s]" (SprinkerForecastDay1) {channel="hydrawise:controller:myaccount:123456:forecast1#conditions"}
|
||||||
|
String SprinkerForecastDay1Day "Day [%s]" (SprinkerForecastDay1) {channel="hydrawise:controller:myaccount:123456:forecast1#day"}
|
||||||
|
Number SprinkerForecastDay1Humidity "Humidity [%d%%]" (SprinkerForecastDay1) {channel="hydrawise:controller:myaccount:123456:forecast1#humidity"}
|
||||||
|
Number:Speed SprinkerForecastDay1Wind "Wind [%s]" (SprinkerForecastDay1) {channel="hydrawise:controller:myaccount:123456:forecast1#wind"}
|
||||||
|
|
||||||
Group SprinklerZone1 "1 Front Office Yard" (SprinklerZones)
|
Group SprinklerZone1 "1 Front Office Yard" (SprinklerZones)
|
||||||
String SprinklerZone1Name "1 Front Office Yard name" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#name"}
|
String SprinklerZone1Name "1 Front Office Yard name" (SprinklerZone1) {channel="hydrawise:controller:myaccount:123456:zone1#name"}
|
||||||
Switch SprinklerZone1Run "1 Front Office Yard Run" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#run"}
|
Switch SprinklerZone1Run "1 Front Office Yard Run" (SprinklerZone1) {channel="hydrawise:controller:myaccount:123456:zone1#run"}
|
||||||
Switch SprinklerZone1RunLocal "1 Front Office Yard Run (local)" (SprinklerZone1) {channel="hydrawise:local:home:zone1#run"}
|
Switch SprinklerZone1RunLocal "1 Front Office Yard Run (local)" (SprinklerZone1) {channel="hydrawise:local:home:zone1#run"}
|
||||||
Number SprinklerZone1RunCustom "1 Front Office Yard Run Custom" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#runcustom"}
|
Number SprinklerZone1RunCustom "1 Front Office Yard Run Custom" (SprinklerZone1) {channel="hydrawise:controller:myaccount:123456:zone1#runcustom"}
|
||||||
DateTime SprinklerZone1StartTime "1 Front Office Yard Start Time [%s]" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#nextruntime"}
|
DateTime SprinklerZone1StartTime "1 Front Office Yard Start Time [%s]" (SprinklerZone1) {channel="hydrawise:controller:myaccount:123456:zone1#nextruntime"}
|
||||||
Number SprinklerZone1TimeLeft "1 Front Office Yard Time Left" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#timeleft"}
|
Number SprinklerZone1TimeLeft "1 Front Office Yard Time Left" (SprinklerZone1) {channel="hydrawise:controller:myaccount:123456:zone1#timeleft"}
|
||||||
String SprinklerZone1Icon "1 Front Office Yard Icon" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#icon"}
|
String SprinklerZone1Icon "1 Front Office Yard Icon" (SprinklerZone1) {channel="hydrawise:controller:myaccount:123456:zone1#icon"}
|
||||||
|
|
||||||
Group SprinklerZone2 "2 Back Circle Lawn" (SprinklerZones)
|
Group SprinklerZone2 "2 Back Circle Lawn" (SprinklerZones)
|
||||||
String SprinklerZone2Name "2 Back Circle Lawn name" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#name"}
|
String SprinklerZone2Name "2 Back Circle Lawn name" (SprinklerZone2) {channel="hydrawise:controller:myaccount:123456:zone2#name"}
|
||||||
Switch SprinklerZone2Run "2 Back Circle Lawn Run" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#run"}
|
Switch SprinklerZone2Run "2 Back Circle Lawn Run" (SprinklerZone2) {channel="hydrawise:controller:myaccount:123456:zone2#run"}
|
||||||
Switch SprinklerZone2RunLocal "2 Back Circle Lawn Run (local)" (SprinklerZone2) {channel="hydrawise:local:home:zone2#run"}
|
Switch SprinklerZone2RunLocal "2 Back Circle Lawn Run (local)" (SprinklerZone2) {channel="hydrawise:local:home:zone2#run"}
|
||||||
Number SprinklerZone2RunCustom "2 Back Circle Lawn Run Custom" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#runcustom"}
|
Number SprinklerZone2RunCustom "2 Back Circle Lawn Run Custom" (SprinklerZone2) {channel="hydrawise:controller:myaccount:123456:zone2#runcustom"}
|
||||||
DateTime SprinklerZone2StartTime "2 Back Circle Lawn Start Time" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#nextruntime"}
|
DateTime SprinklerZone2StartTime "2 Back Circle Lawn Start Time" (SprinklerZone2) {channel="hydrawise:controller:myaccount:123456:zone2#nextruntime"}
|
||||||
Number SprinklerZone2TimeLeft "2 Back Circle Lawn Time Left" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#timeleft"}
|
Number SprinklerZone2TimeLeft "2 Back Circle Lawn Time Left" (SprinklerZone2) {channel="hydrawise:controller:myaccount:123456:zone2#timeleft"}
|
||||||
String SprinklerZone2Icon "2 Back Circle Lawn Icon" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#icon"}
|
String SprinklerZone2Icon "2 Back Circle Lawn Icon" (SprinklerZone2) {channel="hydrawise:controller:myaccount:123456:zone2#icon"}
|
||||||
|
|
||||||
Group SprinklerZone3 "3 Left of Drive Lawn" (SprinklerZones)
|
Group SprinklerZone3 "3 Left of Drive Lawn" (SprinklerZones)
|
||||||
String SprinklerZone3Name "3 Left of Drive Lawn name" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#name"}
|
String SprinklerZone3Name "3 Left of Drive Lawn name" (SprinklerZone3) {channel="hydrawise:controller:myaccount:123456:zone3#name"}
|
||||||
Switch SprinklerZone3Run "3 Left of Drive Lawn Run" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#run"}
|
Switch SprinklerZone3Run "3 Left of Drive Lawn Run" (SprinklerZone3) {channel="hydrawise:controller:myaccount:123456:zone3#run"}
|
||||||
Switch SprinklerZone3RunLocal "3 Left of Drive Lawn Run (local)" (SprinklerZone3) {channel="hydrawise:local:home:zone3#run"}
|
Switch SprinklerZone3RunLocal "3 Left of Drive Lawn Run (local)" (SprinklerZone3) {channel="hydrawise:local:home:zone3#run"}
|
||||||
Number SprinklerZone3RunCustom "3 Left of Drive Lawn Run Custom" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#runcustom"}
|
Number SprinklerZone3RunCustom "3 Left of Drive Lawn Run Custom" (SprinklerZone3) {channel="hydrawise:controller:myaccount:123456:zone3#runcustom"}
|
||||||
DateTime SprinklerZone3StartTime "3 Left of Drive Lawn Start Time" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#nextruntime"}
|
DateTime SprinklerZone3StartTime "3 Left of Drive Lawn Start Time" (SprinklerZone3) {channel="hydrawise:controller:myaccount:123456:zone3#nextruntime"}
|
||||||
Number SprinklerZone3TimeLeft "3 Left of Drive Lawn Time Left" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#timeleft"}
|
Number SprinklerZone3TimeLeft "3 Left of Drive Lawn Time Left" (SprinklerZone3) {channel="hydrawise:controller:myaccount:123456:zone3#timeleft"}
|
||||||
String SprinklerZone3Icon "3 Left of Drive Lawn Icon" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#icon"}
|
String SprinklerZone3Icon "3 Left of Drive Lawn Icon" (SprinklerZone3) {channel="hydrawise:controller:myaccount:123456:zone3#icon"}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -23,44 +23,57 @@ import org.openhab.core.thing.ThingTypeUID;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class HydrawiseBindingConstants {
|
public class HydrawiseBindingConstants {
|
||||||
|
|
||||||
private static final String BINDING_ID = "hydrawise";
|
private static final String BINDING_ID = "hydrawise";
|
||||||
|
|
||||||
// List of all Thing Type UIDs
|
// List of all Thing Type UIDs
|
||||||
public static final ThingTypeUID THING_TYPE_CLOUD = new ThingTypeUID(BINDING_ID, "cloud");
|
public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||||
|
public static final ThingTypeUID THING_TYPE_CONTROLLER = new ThingTypeUID(BINDING_ID, "controller");
|
||||||
public static final ThingTypeUID THING_TYPE_LOCAL = new ThingTypeUID(BINDING_ID, "local");
|
public static final ThingTypeUID THING_TYPE_LOCAL = new ThingTypeUID(BINDING_ID, "local");
|
||||||
|
|
||||||
public static final String BASE_IMAGE_URL = "https://app.hydrawise.com/config/images/";
|
public static final String BASE_IMAGE_URL = "https://app.hydrawise.com/config/images/";
|
||||||
|
|
||||||
|
public static final String CONFIG_USERNAME = "userName";
|
||||||
|
public static final String CONFIG_PASSWORD = "password";
|
||||||
|
public static final String CONFIG_REFRESHTOKEN = "refreshToken";
|
||||||
|
public static final String CONFIG_CONTROLLER_ID = "controllerId";
|
||||||
|
|
||||||
|
public static final String CHANNEL_GROUP_CONTROLLER_SYSTEM = "system";
|
||||||
|
public static final String CHANNEL_CONTROLLER_NAME = "name";
|
||||||
|
public static final String CHANNEL_CONTROLLER_LAST_CONTACT = "lastcontact";
|
||||||
|
public static final String CHANNEL_CONTROLLER_STATUS = "status";
|
||||||
|
public static final String CHANNEL_CONTROLLER_SUMMARY = "summary";
|
||||||
|
public static final String CHANNEL_CONTROLLER_ONLINE = "online";
|
||||||
public static final String CHANNEL_GROUP_ALLZONES = "allzones";
|
public static final String CHANNEL_GROUP_ALLZONES = "allzones";
|
||||||
public static final String CHANNEL_ZONE_RUN_CUSTOM = "runcustom";
|
|
||||||
public static final String CHANNEL_ZONE_RUN = "run";
|
|
||||||
public static final String CHANNEL_ZONE_STOP = "stop";
|
|
||||||
public static final String CHANNEL_ZONE_SUSPEND = "suspend";
|
|
||||||
public static final String CHANNEL_ZONE_NAME = "name";
|
public static final String CHANNEL_ZONE_NAME = "name";
|
||||||
public static final String CHANNEL_ZONE_ICON = "icon";
|
public static final String CHANNEL_ZONE_ICON = "icon";
|
||||||
public static final String CHANNEL_ZONE_LAST_WATER = "lastwater";
|
public static final String CHANNEL_ZONE_STARTTIME = "starttime";
|
||||||
public static final String CHANNEL_ZONE_TIME = "time";
|
public static final String CHANNEL_ZONE_DURATION = "duration";
|
||||||
public static final String CHANNEL_ZONE_TYPE = "type";
|
public static final String CHANNEL_ZONE_TYPE = "type";
|
||||||
|
public static final String CHANNEL_ZONE_RUN = "run";
|
||||||
|
public static final String CHANNEL_ZONE_RUN_CUSTOM = "runcustom";
|
||||||
public static final String CHANNEL_ZONE_NEXT_RUN_TIME_TIME = "nextruntime";
|
public static final String CHANNEL_ZONE_NEXT_RUN_TIME_TIME = "nextruntime";
|
||||||
|
public static final String CHANNEL_ZONE_SUSPEND = "suspend";
|
||||||
|
public static final String CHANNEL_ZONE_SUSPENDUNTIL = "suspenduntil";
|
||||||
|
public static final String CHANNEL_ZONE_SUMMARY = "summary";
|
||||||
public static final String CHANNEL_ZONE_TIME_LEFT = "timeleft";
|
public static final String CHANNEL_ZONE_TIME_LEFT = "timeleft";
|
||||||
public static final String CHANNEL_RUN_ALL_ZONES = "runall";
|
|
||||||
public static final String CHANNEL_STOP_ALL_ZONES = "stopall";
|
|
||||||
public static final String CHANNEL_SUSPEND_ALL_ZONES = "suspendall";
|
|
||||||
public static final String CHANNEL_SENSOR_NAME = "name";
|
public static final String CHANNEL_SENSOR_NAME = "name";
|
||||||
public static final String CHANNEL_SENSOR_INPUT = "input";
|
public static final String CHANNEL_SENSOR_INPUT = "input";
|
||||||
public static final String CHANNEL_SENSOR_MODE = "mode";
|
public static final String CHANNEL_SENSOR_MODE = "mode";
|
||||||
public static final String CHANNEL_SENSOR_TIMER = "timer";
|
public static final String CHANNEL_SENSOR_DELAY = "delay";
|
||||||
public static final String CHANNEL_SENSOR_OFFTIMER = "offtimer";
|
public static final String CHANNEL_SENSOR_OFFTIMER = "offtimer";
|
||||||
public static final String CHANNEL_SENSOR_OFFLEVEL = "offlevel";
|
public static final String CHANNEL_SENSOR_OFFLEVEL = "offlevel";
|
||||||
public static final String CHANNEL_SENSOR_ACTIVE = "active";
|
public static final String CHANNEL_SENSOR_ACTIVE = "active";
|
||||||
|
public static final String CHANNEL_SENSOR_WATERFLOW = "waterflow";
|
||||||
public static final String CHANNEL_FORECAST_TEMPERATURE_HIGH = "temperaturehigh";
|
public static final String CHANNEL_FORECAST_TEMPERATURE_HIGH = "temperaturehigh";
|
||||||
public static final String CHANNEL_FORECAST_TEMPERATURE_LOW = "temperaturelow";
|
public static final String CHANNEL_FORECAST_TEMPERATURE_LOW = "temperaturelow";
|
||||||
public static final String CHANNEL_FORECAST_CONDITIONS = "conditions";
|
public static final String CHANNEL_FORECAST_CONDITIONS = "conditions";
|
||||||
public static final String CHANNEL_FORECAST_DAY = "day";
|
public static final String CHANNEL_FORECAST_TIME = "time";
|
||||||
public static final String CHANNEL_FORECAST_HUMIDITY = "humidity";
|
public static final String CHANNEL_FORECAST_HUMIDITY = "humidity";
|
||||||
public static final String CHANNEL_FORECAST_WIND = "wind";
|
public static final String CHANNEL_FORECAST_WIND = "wind";
|
||||||
public static final String CHANNEL_FORECAST_ICON = "icon";
|
public static final String CHANNEL_FORECAST_ICON = "icon";
|
||||||
|
public static final String CHANNEL_FORECAST_EVAPOTRANSPRIATION = "evapotranspiration";
|
||||||
|
public static final String CHANNEL_FORECAST_PRECIPITATION = "precipitation";
|
||||||
|
public static final String CHANNEL_FORECAST_PROBABILITYOFPRECIPITATION = "probabilityofprecipitation";
|
||||||
|
|
||||||
public static final String PROPERTY_CONTROLLER_ID = "controller";
|
public static final String PROPERTY_CONTROLLER_ID = "controller";
|
||||||
public static final String PROPERTY_NAME = "name";
|
public static final String PROPERTY_NAME = "name";
|
||||||
public static final String PROPERTY_DESCRIPTION = "description";
|
public static final String PROPERTY_DESCRIPTION = "description";
|
||||||
|
|||||||
@ -1,243 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.hydrawise.internal;
|
|
||||||
|
|
||||||
import static org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNull;
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseCloudApiClient;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseCommandException;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.Controller;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.CustomerDetailsResponse;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.Forecast;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.Relay;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.StatusScheduleResponse;
|
|
||||||
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.library.unit.ImperialUnits;
|
|
||||||
import org.openhab.core.library.unit.SIUnits;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link HydrawiseCloudHandler} is responsible for handling commands, which are
|
|
||||||
* sent to one of the channels.
|
|
||||||
*
|
|
||||||
* @author Dan Cunningham - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class HydrawiseCloudHandler extends HydrawiseHandler {
|
|
||||||
/**
|
|
||||||
* 74.2 F
|
|
||||||
*/
|
|
||||||
private static final Pattern TEMPERATURE_PATTERN = Pattern.compile("^(\\d{1,3}.?\\d?)\\s([C,F])");
|
|
||||||
/**
|
|
||||||
* 9 mph
|
|
||||||
*/
|
|
||||||
private static final Pattern WIND_SPEED_PATTERN = Pattern.compile("^(\\d{1,3})\\s([a-z]{3})");
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(HydrawiseCloudHandler.class);
|
|
||||||
private HydrawiseCloudApiClient client;
|
|
||||||
private int controllerId;
|
|
||||||
|
|
||||||
public HydrawiseCloudHandler(Thing thing, HttpClient httpClient) {
|
|
||||||
super(thing);
|
|
||||||
this.client = new HydrawiseCloudApiClient(httpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure()
|
|
||||||
throws NotConfiguredException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
HydrawiseCloudConfiguration configuration = getConfig().as(HydrawiseCloudConfiguration.class);
|
|
||||||
|
|
||||||
this.refresh = Math.max(configuration.refresh, MIN_REFRESH_SECONDS);
|
|
||||||
|
|
||||||
client.setApiKey(configuration.apiKey);
|
|
||||||
|
|
||||||
CustomerDetailsResponse customerDetails = client.getCustomerDetails();
|
|
||||||
|
|
||||||
List<Controller> controllers = customerDetails.controllers;
|
|
||||||
if (controllers.isEmpty()) {
|
|
||||||
throw new NotConfiguredException("No controllers found on account");
|
|
||||||
}
|
|
||||||
|
|
||||||
Controller controller = null;
|
|
||||||
// try and use ID from user configuration
|
|
||||||
if (configuration.controllerId != null) {
|
|
||||||
controller = getController(configuration.controllerId.intValue(), controllers);
|
|
||||||
if (controller == null) {
|
|
||||||
throw new NotConfiguredException("No controller found for id " + configuration.controllerId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// try and use ID from saved property
|
|
||||||
String controllerId = getThing().getProperties().get(PROPERTY_CONTROLLER_ID);
|
|
||||||
if (controllerId != null && !controllerId.isBlank()) {
|
|
||||||
try {
|
|
||||||
controller = getController(Integer.parseInt(controllerId), controllers);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
logger.debug("Can not parse property vaue {}", controllerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// use current controller ID
|
|
||||||
if (controller == null) {
|
|
||||||
controller = getController(customerDetails.controllerId, controllers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controller == null) {
|
|
||||||
throw new NotConfiguredException("No controller found");
|
|
||||||
}
|
|
||||||
|
|
||||||
controllerId = controller.controllerId.intValue();
|
|
||||||
updateControllerProperties(controller);
|
|
||||||
logger.debug("Controller id {}", controllerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Poll the controller for updates.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void pollController() throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
List<Controller> controllers = client.getCustomerDetails().controllers;
|
|
||||||
Controller controller = getController(controllerId, controllers);
|
|
||||||
if (controller != null && !controller.online) {
|
|
||||||
throw new HydrawiseConnectionException("Controller is offline");
|
|
||||||
}
|
|
||||||
StatusScheduleResponse status = client.getStatusSchedule(controllerId);
|
|
||||||
updateSensors(status);
|
|
||||||
updateForecast(status);
|
|
||||||
updateZones(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendRunCommand(int seconds, @Nullable Relay relay)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
if (relay != null) {
|
|
||||||
client.runRelay(seconds, relay.relayId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendRunCommand(@Nullable Relay relay)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
if (relay != null) {
|
|
||||||
client.runRelay(relay.relayId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendStopCommand(@Nullable Relay relay)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
if (relay != null) {
|
|
||||||
client.stopRelay(relay.relayId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendRunAllCommand()
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
client.runAllRelays(controllerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendRunAllCommand(int seconds)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
client.runAllRelays(seconds, controllerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendStopAllCommand()
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
client.stopAllRelays(controllerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSensors(StatusScheduleResponse status) {
|
|
||||||
status.sensors.forEach(sensor -> {
|
|
||||||
String group = "sensor" + sensor.input;
|
|
||||||
updateGroupState(group, CHANNEL_SENSOR_MODE, new DecimalType(sensor.type));
|
|
||||||
updateGroupState(group, CHANNEL_SENSOR_NAME, new StringType(sensor.name));
|
|
||||||
updateGroupState(group, CHANNEL_SENSOR_OFFTIMER, new DecimalType(sensor.offtimer));
|
|
||||||
updateGroupState(group, CHANNEL_SENSOR_TIMER, new DecimalType(sensor.timer));
|
|
||||||
// Some fields are missing depending on sensor type.
|
|
||||||
if (sensor.offlevel != null) {
|
|
||||||
updateGroupState(group, CHANNEL_SENSOR_OFFLEVEL, new DecimalType(sensor.offlevel));
|
|
||||||
}
|
|
||||||
if (sensor.active != null) {
|
|
||||||
updateGroupState(group, CHANNEL_SENSOR_ACTIVE, sensor.active > 0 ? OnOffType.ON : OnOffType.OFF);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateForecast(StatusScheduleResponse status) {
|
|
||||||
int i = 1;
|
|
||||||
for (Forecast forecast : status.forecast) {
|
|
||||||
String group = "forecast" + (i++);
|
|
||||||
updateGroupState(group, CHANNEL_FORECAST_CONDITIONS, new StringType(forecast.conditions));
|
|
||||||
updateGroupState(group, CHANNEL_FORECAST_DAY, new StringType(forecast.day));
|
|
||||||
updateGroupState(group, CHANNEL_FORECAST_HUMIDITY, new DecimalType(forecast.humidity));
|
|
||||||
updateTemperature(forecast.tempHi, group, CHANNEL_FORECAST_TEMPERATURE_HIGH);
|
|
||||||
updateTemperature(forecast.tempLo, group, CHANNEL_FORECAST_TEMPERATURE_LOW);
|
|
||||||
updateWindspeed(forecast.wind, group, CHANNEL_FORECAST_WIND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTemperature(String tempString, String group, String channel) {
|
|
||||||
Matcher matcher = TEMPERATURE_PATTERN.matcher(tempString);
|
|
||||||
if (matcher.matches()) {
|
|
||||||
try {
|
|
||||||
updateGroupState(group, channel, new QuantityType<>(Double.valueOf(matcher.group(1)),
|
|
||||||
"C".equals(matcher.group(2)) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
logger.debug("Could not parse temperature string {} ", tempString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateWindspeed(String windString, String group, String channel) {
|
|
||||||
Matcher matcher = WIND_SPEED_PATTERN.matcher(windString);
|
|
||||||
if (matcher.matches()) {
|
|
||||||
try {
|
|
||||||
updateGroupState(group, channel, new QuantityType<>(Integer.parseInt(matcher.group(1)),
|
|
||||||
"kph".equals(matcher.group(2)) ? SIUnits.KILOMETRE_PER_HOUR : ImperialUnits.MILES_PER_HOUR));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
logger.debug("Could not parse wind string {} ", windString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateControllerProperties(Controller controller) {
|
|
||||||
getThing().setProperty(PROPERTY_CONTROLLER_ID, String.valueOf(controller.controllerId));
|
|
||||||
getThing().setProperty(PROPERTY_NAME, controller.name);
|
|
||||||
getThing().setProperty(PROPERTY_DESCRIPTION, controller.description);
|
|
||||||
getThing().setProperty(PROPERTY_LOCATION, controller.latitude + "," + controller.longitude);
|
|
||||||
getThing().setProperty(PROPERTY_ADDRESS, controller.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable Controller getController(int controllerId, List<Controller> controllers) {
|
|
||||||
Optional<@NonNull Controller> optionalController = controllers.stream()
|
|
||||||
.filter(c -> controllerId == c.controllerId.intValue()).findAny();
|
|
||||||
return optionalController.isPresent() ? optionalController.get() : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public interface HydrawiseControllerListener {
|
||||||
|
public void onData(List<Controller> controllers);
|
||||||
|
}
|
||||||
@ -15,13 +15,16 @@ package org.openhab.binding.hydrawise.internal;
|
|||||||
import static org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants.*;
|
import static org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants.*;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.openhab.binding.hydrawise.internal.handler.HydrawiseAccountHandler;
|
||||||
|
import org.openhab.binding.hydrawise.internal.handler.HydrawiseControllerHandler;
|
||||||
|
import org.openhab.binding.hydrawise.internal.handler.HydrawiseLocalHandler;
|
||||||
|
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
import org.openhab.core.thing.Bridge;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||||
@ -40,14 +43,15 @@ import org.osgi.service.component.annotations.Reference;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(configurationPid = "binding.hydrawise", service = ThingHandlerFactory.class)
|
@Component(configurationPid = "binding.hydrawise", service = ThingHandlerFactory.class)
|
||||||
public class HydrawiseHandlerFactory extends BaseThingHandlerFactory {
|
public class HydrawiseHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT,
|
||||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream.of(THING_TYPE_CLOUD, THING_TYPE_LOCAL)
|
THING_TYPE_CONTROLLER, THING_TYPE_LOCAL);
|
||||||
.collect(Collectors.toSet());
|
private HttpClient httpClient;
|
||||||
|
private OAuthFactory oAuthFactory;
|
||||||
private final HttpClient httpClient;
|
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public HydrawiseHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
|
public HydrawiseHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||||
|
final @Reference OAuthFactory oAuthFactory) {
|
||||||
|
this.oAuthFactory = oAuthFactory;
|
||||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,8 +64,12 @@ public class HydrawiseHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
|
||||||
if (THING_TYPE_CLOUD.equals(thingTypeUID)) {
|
if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) {
|
||||||
return new HydrawiseCloudHandler(thing, httpClient);
|
return new HydrawiseAccountHandler((Bridge) thing, httpClient, oAuthFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (THING_TYPE_CONTROLLER.equals(thingTypeUID)) {
|
||||||
|
return new HydrawiseControllerHandler(thing);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (THING_TYPE_LOCAL.equals(thingTypeUID)) {
|
if (THING_TYPE_LOCAL.equals(thingTypeUID)) {
|
||||||
|
|||||||
@ -1,91 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.hydrawise.internal;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseCommandException;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseLocalApiClient;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.Relay;
|
|
||||||
import org.openhab.core.thing.Thing;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link HydrawiseLocalHandler} is responsible for handling commands, which are
|
|
||||||
* sent to one of the channels.
|
|
||||||
*
|
|
||||||
* @author Dan Cunningham - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class HydrawiseLocalHandler extends HydrawiseHandler {
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(HydrawiseLocalHandler.class);
|
|
||||||
HydrawiseLocalApiClient client;
|
|
||||||
|
|
||||||
public HydrawiseLocalHandler(Thing thing, HttpClient httpClient) {
|
|
||||||
super(thing);
|
|
||||||
client = new HydrawiseLocalApiClient(httpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure() throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
HydrawiseLocalConfiguration configuration = getConfig().as(HydrawiseLocalConfiguration.class);
|
|
||||||
this.refresh = Math.max(configuration.refresh, MIN_REFRESH_SECONDS);
|
|
||||||
logger.trace("Connecting to host {}", configuration.host);
|
|
||||||
client.setCredentials(configuration.host, configuration.username, configuration.password);
|
|
||||||
pollController();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void pollController() throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
updateZones(client.getLocalSchedule());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendRunCommand(int seconds, Relay relay)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
client.runRelay(seconds, relay.relay);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendRunCommand(Relay relay)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
client.runRelay(relay.relay);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendStopCommand(Relay relay)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
client.stopRelay(relay.relay);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendRunAllCommand()
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
client.runAllRelays();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendRunAllCommand(int seconds)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
client.runAllRelays(seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendStopAllCommand()
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
client.stopAllRelays();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -12,12 +12,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api;
|
package org.openhab.binding.hydrawise.internal.api;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when the Hydrawise cloud or local API returns back a "unauthorized" response to commands
|
* Thrown when the Hydrawise API returns back a "unauthorized" response to commands
|
||||||
*
|
*
|
||||||
* @author Dan Cunningham - Initial contribution
|
* @author Dan Cunningham - Initial contribution
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@NonNullByDefault
|
||||||
public class HydrawiseAuthenticationException extends Exception {
|
public class HydrawiseAuthenticationException extends Exception {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public HydrawiseAuthenticationException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HydrawiseAuthenticationException(@Nullable String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,312 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.hydrawise.internal.api;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
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.http.HttpMethod;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.CustomerDetailsResponse;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.Response;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.SetControllerResponse;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.SetZoneResponse;
|
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.StatusScheduleResponse;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.gson.FieldNamingPolicy;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link HydrawiseCloudApiClient} communicates with the cloud based Hydrawise API service
|
|
||||||
*
|
|
||||||
* @author Dan Cunningham - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class HydrawiseCloudApiClient {
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(HydrawiseCloudApiClient.class);
|
|
||||||
|
|
||||||
private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
|
||||||
.create();
|
|
||||||
private static final String BASE_URL = "https://app.hydrawise.com/api/v1/";
|
|
||||||
private static final String STATUS_SCHEDUE_URL = BASE_URL
|
|
||||||
+ "statusschedule.php?api_key=%s&controller_id=%d&hours=168";
|
|
||||||
private static final String CUSTOMER_DETAILS_URL = BASE_URL + "customerdetails.php?api_key=%s&type=controllers";
|
|
||||||
private static final String SET_CONTROLLER_URL = BASE_URL
|
|
||||||
+ "setcontroller.php?api_key=%s&controller_id=%d&json=true";
|
|
||||||
private static final String SET_ZONE_URL = BASE_URL + "setzone.php?period_id=999";
|
|
||||||
private static final int TIMEOUT_SECONDS = 30;
|
|
||||||
private final HttpClient httpClient;
|
|
||||||
private String apiKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the API client with a HydraWise API key from a user's account and the HTTPClient to use
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public HydrawiseCloudApiClient(String apiKey, HttpClient httpClient) {
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
this.httpClient = httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the API client with a HTTPClient to use
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public HydrawiseCloudApiClient(HttpClient httpClient) {
|
|
||||||
this("", httpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a new API key to use for requests
|
|
||||||
*
|
|
||||||
* @param apiKey
|
|
||||||
*/
|
|
||||||
public void setApiKey(String apiKey) {
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the {@link StatusScheduleResponse} for a given controller
|
|
||||||
*
|
|
||||||
* @param controllerId
|
|
||||||
* @return
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
*/
|
|
||||||
public StatusScheduleResponse getStatusSchedule(int controllerId)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
String json = doGet(String.format(STATUS_SCHEDUE_URL, apiKey, controllerId));
|
|
||||||
StatusScheduleResponse response = Objects.requireNonNull(gson.fromJson(json, StatusScheduleResponse.class));
|
|
||||||
throwExceptionIfResponseError(response);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Retrieves the {@link CustomerDetailsResponse}
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
*/
|
|
||||||
public CustomerDetailsResponse getCustomerDetails()
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
String json = doGet(String.format(CUSTOMER_DETAILS_URL, apiKey));
|
|
||||||
CustomerDetailsResponse response = Objects.requireNonNull(gson.fromJson(json, CustomerDetailsResponse.class));
|
|
||||||
throwExceptionIfResponseError(response);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Sets the controller with supplied {@param id} as the current controller
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @return SetControllerResponse
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
* @throws HydrawiseCommandException
|
|
||||||
*/
|
|
||||||
public SetControllerResponse setController(int id)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
String json = doGet(String.format(SET_CONTROLLER_URL, apiKey, id));
|
|
||||||
SetControllerResponse response = Objects.requireNonNull(gson.fromJson(json, SetControllerResponse.class));
|
|
||||||
throwExceptionIfResponseError(response);
|
|
||||||
if (!response.message.equals("OK")) {
|
|
||||||
throw new HydrawiseCommandException(response.message);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Stops a given relay
|
|
||||||
*
|
|
||||||
* @param relayId
|
|
||||||
* @return Response message
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
* @throws HydrawiseCommandException
|
|
||||||
*/
|
|
||||||
public String stopRelay(int relayId)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
return relayCommand(
|
|
||||||
new HydrawiseZoneCommandBuilder(SET_ZONE_URL, apiKey).action("stop").relayId(relayId).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops all relays on a given controller
|
|
||||||
*
|
|
||||||
* @param controllerId
|
|
||||||
* @return Response message
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
* @throws HydrawiseCommandException
|
|
||||||
*/
|
|
||||||
public String stopAllRelays(int controllerId)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
return relayCommand(new HydrawiseZoneCommandBuilder(SET_ZONE_URL, apiKey).action("stopall")
|
|
||||||
.controllerId(controllerId).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs a relay for the default amount of time
|
|
||||||
*
|
|
||||||
* @param relayId
|
|
||||||
* @return Response message
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
* @throws HydrawiseCommandException
|
|
||||||
*/
|
|
||||||
public String runRelay(int relayId)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
return relayCommand(
|
|
||||||
new HydrawiseZoneCommandBuilder(SET_ZONE_URL, apiKey).action("run").relayId(relayId).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs a relay for the given amount of seconds
|
|
||||||
*
|
|
||||||
* @param seconds
|
|
||||||
* @param relayId
|
|
||||||
* @return Response message
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
* @throws HydrawiseCommandException
|
|
||||||
*/
|
|
||||||
public String runRelay(int seconds, int relayId)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
return relayCommand(new HydrawiseZoneCommandBuilder(SET_ZONE_URL, apiKey).action("run").relayId(relayId)
|
|
||||||
.duration(seconds).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run all relays on a given controller for the default amount of time
|
|
||||||
*
|
|
||||||
* @param controllerId
|
|
||||||
* @return Response message
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
* @throws HydrawiseCommandException
|
|
||||||
*/
|
|
||||||
public String runAllRelays(int controllerId)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
return relayCommand(new HydrawiseZoneCommandBuilder(SET_ZONE_URL, apiKey).action("runall")
|
|
||||||
.controllerId(controllerId).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Run all relays on a given controller for the amount of seconds
|
|
||||||
*
|
|
||||||
* @param seconds
|
|
||||||
* @param controllerId
|
|
||||||
* @return Response message
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
* @throws HydrawiseCommandException
|
|
||||||
*/
|
|
||||||
public String runAllRelays(int seconds, int controllerId)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
return relayCommand(new HydrawiseZoneCommandBuilder(SET_ZONE_URL, apiKey).action("runall")
|
|
||||||
.controllerId(controllerId).duration(seconds).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Suspends a given relay
|
|
||||||
*
|
|
||||||
* @param relayId
|
|
||||||
* @return Response message
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
* @throws HydrawiseCommandException
|
|
||||||
*/
|
|
||||||
public String suspendRelay(int relayId)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
return relayCommand(
|
|
||||||
new HydrawiseZoneCommandBuilder(SET_ZONE_URL, apiKey).action("suspend").relayId(relayId).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Suspends a given relay for an amount of seconds
|
|
||||||
*
|
|
||||||
* @param seconds
|
|
||||||
* @param relayId
|
|
||||||
* @return Response message
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
* @throws HydrawiseCommandException
|
|
||||||
*/
|
|
||||||
public String suspendRelay(int seconds, int relayId)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
return relayCommand(new HydrawiseZoneCommandBuilder(SET_ZONE_URL, apiKey).action("suspend").relayId(relayId)
|
|
||||||
.duration(seconds).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Suspend all relays on a given controller for an amount of seconds
|
|
||||||
*
|
|
||||||
* @param seconds
|
|
||||||
* @param controllerId
|
|
||||||
* @return Response message
|
|
||||||
* @throws HydrawiseConnectionException
|
|
||||||
* @throws HydrawiseAuthenticationException
|
|
||||||
* @throws HydrawiseCommandException
|
|
||||||
*/
|
|
||||||
public String suspendAllRelays(int seconds, int controllerId)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
return relayCommand(new HydrawiseZoneCommandBuilder(SET_ZONE_URL, apiKey).action("suspendall")
|
|
||||||
.controllerId(controllerId).duration(seconds).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String relayCommand(String url)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
|
||||||
String json = doGet(url);
|
|
||||||
SetZoneResponse response = Objects.requireNonNull(gson.fromJson(json, SetZoneResponse.class));
|
|
||||||
throwExceptionIfResponseError(response);
|
|
||||||
if ("error".equals(response.messageType)) {
|
|
||||||
throw new HydrawiseCommandException(response.message);
|
|
||||||
}
|
|
||||||
return response.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String doGet(String url) throws HydrawiseConnectionException {
|
|
||||||
logger.trace("Getting {}", url);
|
|
||||||
ContentResponse response;
|
|
||||||
try {
|
|
||||||
response = httpClient.newRequest(url).method(HttpMethod.GET).timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
|
||||||
.send();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new HydrawiseConnectionException(e);
|
|
||||||
}
|
|
||||||
if (response.getStatus() != 200) {
|
|
||||||
throw new HydrawiseConnectionException(
|
|
||||||
"Could not connect to Hydrawise API. Response code " + response.getStatus());
|
|
||||||
}
|
|
||||||
String stringResponse = response.getContentAsString();
|
|
||||||
logger.trace("Response: {}", stringResponse);
|
|
||||||
return stringResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throwExceptionIfResponseError(Response response)
|
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
|
||||||
String error = response.errorMsg;
|
|
||||||
if (error != null) {
|
|
||||||
if (error.equalsIgnoreCase("unauthorized")) {
|
|
||||||
throw new HydrawiseAuthenticationException();
|
|
||||||
} else {
|
|
||||||
throw new HydrawiseConnectionException(response.errorMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -12,13 +12,17 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api;
|
package org.openhab.binding.hydrawise.internal.api;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when command responses return a error message
|
* Thrown when command responses return a error message
|
||||||
*
|
*
|
||||||
* @author Dan Cunningham - Initial contribution
|
* @author Dan Cunningham - Initial contribution
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@NonNullByDefault
|
||||||
public class HydrawiseCommandException extends Exception {
|
public class HydrawiseCommandException extends Exception {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
public HydrawiseCommandException(String message) {
|
public HydrawiseCommandException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,13 +12,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api;
|
package org.openhab.binding.hydrawise.internal.api;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown for connection issues to the Hydrawise controller
|
* Thrown for connection issues to the Hydrawise controller
|
||||||
*
|
*
|
||||||
* @author Dan Cunningham - Initial contribution
|
* @author Dan Cunningham - Initial contribution
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@NonNullByDefault
|
||||||
public class HydrawiseConnectionException extends Exception {
|
public class HydrawiseConnectionException extends Exception {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
public HydrawiseConnectionException(Exception e) {
|
public HydrawiseConnectionException(Exception e) {
|
||||||
super(e);
|
super(e);
|
||||||
|
|||||||
@ -0,0 +1,341 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseCommandException;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.ControllerStatus;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Forecast;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Mutation;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.MutationResponse;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.MutationResponse.MutationResponseStatus;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.MutationResponse.StatusCode;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.QueryRequest;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.QueryResponse;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.ScheduledRuns;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Sensor;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Zone;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.ZoneRun;
|
||||||
|
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||||
|
import org.openhab.core.auth.client.oauth2.OAuthClientService;
|
||||||
|
import org.openhab.core.auth.client.oauth2.OAuthException;
|
||||||
|
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.FieldNamingPolicy;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class HydrawiseGraphQLClient {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(HydrawiseGraphQLClient.class);
|
||||||
|
|
||||||
|
private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
|
.registerTypeAdapter(Zone.class, new ResponseDeserializer<Zone>())
|
||||||
|
.registerTypeAdapter(ScheduledRuns.class, new ResponseDeserializer<ScheduledRuns>())
|
||||||
|
.registerTypeAdapter(ZoneRun.class, new ResponseDeserializer<ZoneRun>())
|
||||||
|
.registerTypeAdapter(Forecast.class, new ResponseDeserializer<Forecast>())
|
||||||
|
.registerTypeAdapter(Sensor.class, new ResponseDeserializer<Forecast>())
|
||||||
|
.registerTypeAdapter(ControllerStatus.class, new ResponseDeserializer<ControllerStatus>()).create();
|
||||||
|
|
||||||
|
private static final String GRAPH_URL = "https://app.hydrawise.com/api/v2/graph";
|
||||||
|
private static final String MUTATION_START_ZONE = "startZone(zoneId: %d) { status }";
|
||||||
|
private static final String MUTATION_START_ZONE_CUSTOM = "startZone(zoneId: %d, customRunDuration: %d) { status }";
|
||||||
|
private static final String MUTATION_START_ALL_ZONES = "startAllZones(controllerId: %d){ status }";
|
||||||
|
private static final String MUTATION_START_ALL_ZONES_CUSTOM = "startAllZones(controllerId: %d, markRunAsScheduled: false, customRunDuration: %d ){ status }";
|
||||||
|
private static final String MUTATION_STOP_ZONE = "stopZone(zoneId: %d) { status }";
|
||||||
|
private static final String MUTATION_STOP_ALL_ZONES = "stopAllZones(controllerId: %d){ status }";
|
||||||
|
private static final String MUTATION_SUSPEND_ZONE = "suspendZone(zoneId: %d, until: \"%s\"){ status }";
|
||||||
|
private static final String MUTATION_SUSPEND_ALL_ZONES = "suspendAllZones(controllerId: %d, until: \"%s\"){ status }";
|
||||||
|
private static final String MUTATION_RESUME_ZONE = "resumeZone(zoneId: %d){ status }";
|
||||||
|
private static final String MUTATION_RESUME_ALL_ZONES = "resumeAllZones(controllerId: %d){ status }";
|
||||||
|
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
private final OAuthClientService oAuthService;
|
||||||
|
private String queryString = "";
|
||||||
|
|
||||||
|
public HydrawiseGraphQLClient(HttpClient httpClient, OAuthClientService oAuthService) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
this.oAuthService = oAuthService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a GrapQL query for controller data
|
||||||
|
*
|
||||||
|
* @return QueryResponse
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
*/
|
||||||
|
public @Nullable QueryResponse queryControllers()
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
||||||
|
QueryRequest query;
|
||||||
|
try {
|
||||||
|
query = new QueryRequest(getQueryString());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new HydrawiseConnectionException(e);
|
||||||
|
}
|
||||||
|
String queryJson = gson.toJson(query);
|
||||||
|
String response = sendGraphQLQuery(queryJson);
|
||||||
|
return gson.fromJson(response, QueryResponse.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Stops a given relay
|
||||||
|
*
|
||||||
|
* @param relayId
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
* @throws HydrawiseCommandException
|
||||||
|
*/
|
||||||
|
public void stopRelay(int relayId)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
sendGraphQLMutation(String.format(MUTATION_STOP_ZONE, relayId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops all relays on a given controller
|
||||||
|
*
|
||||||
|
* @param controllerId
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
* @throws HydrawiseCommandException
|
||||||
|
*/
|
||||||
|
public void stopAllRelays(int controllerId)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
sendGraphQLMutation(String.format(MUTATION_STOP_ALL_ZONES, controllerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a relay for the default amount of time
|
||||||
|
*
|
||||||
|
* @param relayId
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
* @throws HydrawiseCommandException
|
||||||
|
*/
|
||||||
|
public void runRelay(int relayId)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
sendGraphQLMutation(String.format(MUTATION_START_ZONE, relayId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a relay for the given amount of seconds
|
||||||
|
*
|
||||||
|
* @param relayId
|
||||||
|
* @param seconds
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
* @throws HydrawiseCommandException
|
||||||
|
*/
|
||||||
|
public void runRelay(int relayId, int seconds)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
sendGraphQLMutation(String.format(MUTATION_START_ZONE_CUSTOM, relayId, seconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run all relays on a given controller for the default amount of time
|
||||||
|
*
|
||||||
|
* @param controllerId
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
* @throws HydrawiseCommandException
|
||||||
|
*/
|
||||||
|
public void runAllRelays(int controllerId)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
sendGraphQLMutation(String.format(MUTATION_START_ALL_ZONES, controllerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Run all relays on a given controller for the amount of seconds
|
||||||
|
*
|
||||||
|
* @param controllerId
|
||||||
|
* @param seconds
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
* @throws HydrawiseCommandException
|
||||||
|
*/
|
||||||
|
public void runAllRelays(int controllerId, int seconds)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
sendGraphQLMutation(String.format(MUTATION_START_ALL_ZONES_CUSTOM, controllerId, seconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspends a given relay
|
||||||
|
*
|
||||||
|
* @param relayId
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
* @throws HydrawiseCommandException
|
||||||
|
*/
|
||||||
|
public void suspendRelay(int relayId, String until)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
sendGraphQLMutation(String.format(MUTATION_SUSPEND_ZONE, relayId, until));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes a given relay
|
||||||
|
*
|
||||||
|
* @param relayId
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
* @throws HydrawiseCommandException
|
||||||
|
*/
|
||||||
|
public void resumeRelay(int relayId)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
sendGraphQLMutation(String.format(MUTATION_RESUME_ZONE, relayId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspend all relays on a given controller for an amount of seconds
|
||||||
|
*
|
||||||
|
* @param controllerId
|
||||||
|
* @param until
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
* @throws HydrawiseCommandException
|
||||||
|
*/
|
||||||
|
public void suspendAllRelays(int controllerId, String until)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
sendGraphQLMutation(String.format(MUTATION_SUSPEND_ALL_ZONES, controllerId, until));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes all relays on a given controller
|
||||||
|
*
|
||||||
|
* @param controllerId
|
||||||
|
* @throws HydrawiseConnectionException
|
||||||
|
* @throws HydrawiseAuthenticationException
|
||||||
|
* @throws HydrawiseCommandException
|
||||||
|
*/
|
||||||
|
public void resumeAllRelays(int controllerId)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
sendGraphQLMutation(String.format(MUTATION_RESUME_ALL_ZONES, controllerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sendGraphQLQuery(String content)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
||||||
|
return sendGraphQLRequest(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendGraphQLMutation(String content)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
|
||||||
|
Mutation mutation = new Mutation(content);
|
||||||
|
logger.debug("Sending Mutation {}", gson.toJson(mutation).toString());
|
||||||
|
String response = sendGraphQLRequest(gson.toJson(mutation).toString());
|
||||||
|
logger.debug("Mutation response {}", response);
|
||||||
|
MutationResponse mResponse = gson.fromJson(response, MutationResponse.class);
|
||||||
|
if (mResponse == null) {
|
||||||
|
throw new HydrawiseCommandException("Malformed response: " + response);
|
||||||
|
}
|
||||||
|
Optional<MutationResponseStatus> status = mResponse.data.values().stream().findFirst();
|
||||||
|
if (!status.isPresent()) {
|
||||||
|
throw new HydrawiseCommandException("Unknown response: " + response);
|
||||||
|
}
|
||||||
|
if (status.get().status != StatusCode.OK) {
|
||||||
|
throw new HydrawiseCommandException("Command Status: " + status.get().status.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sendGraphQLRequest(String content)
|
||||||
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
||||||
|
logger.trace("Sending Request: {}", content);
|
||||||
|
ContentResponse response;
|
||||||
|
final AtomicInteger responseCode = new AtomicInteger(0);
|
||||||
|
final StringBuilder responseMessage = new StringBuilder();
|
||||||
|
try {
|
||||||
|
AccessTokenResponse token = oAuthService.getAccessTokenResponse();
|
||||||
|
if (token == null) {
|
||||||
|
throw new HydrawiseAuthenticationException("Login required");
|
||||||
|
}
|
||||||
|
response = httpClient.newRequest(GRAPH_URL).method(HttpMethod.POST)
|
||||||
|
.content(new StringContentProvider(content), "application/json")
|
||||||
|
.header("Authorization", token.getTokenType() + " " + token.getAccessToken())
|
||||||
|
.onResponseFailure(new Response.FailureListener() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@Nullable Response response, @Nullable Throwable failure) {
|
||||||
|
int status = response != null ? response.getStatus() : -1;
|
||||||
|
String reason = response != null ? response.getReason() : "Null response";
|
||||||
|
logger.trace("onFailure code: {} message: {}", status, reason);
|
||||||
|
responseCode.set(status);
|
||||||
|
responseMessage.append(reason);
|
||||||
|
}
|
||||||
|
}).send();
|
||||||
|
String stringResponse = response.getContentAsString();
|
||||||
|
logger.trace("Received Response: {}", stringResponse);
|
||||||
|
return stringResponse;
|
||||||
|
} catch (InterruptedException | TimeoutException | OAuthException | IOException e) {
|
||||||
|
logger.debug("Could not send request", e);
|
||||||
|
throw new HydrawiseConnectionException(e);
|
||||||
|
} catch (OAuthResponseException e) {
|
||||||
|
throw new HydrawiseAuthenticationException(e.getMessage());
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
// Hydrawise returns back a 40x status, but without a valid Realm , so jetty throws an exception,
|
||||||
|
// this allows us to catch this in a callback and handle accordingly
|
||||||
|
switch (responseCode.get()) {
|
||||||
|
case 401:
|
||||||
|
case 403:
|
||||||
|
throw new HydrawiseAuthenticationException(responseMessage.toString());
|
||||||
|
default:
|
||||||
|
throw new HydrawiseConnectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getQueryString() throws IOException {
|
||||||
|
if (queryString.isBlank()) {
|
||||||
|
try (InputStream inputStream = HydrawiseGraphQLClient.class.getClassLoader()
|
||||||
|
.getResourceAsStream("query.graphql");
|
||||||
|
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||||
|
queryString = bufferedReader.lines().collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResponseDeserializer<T> implements JsonDeserializer<T> {
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
|
||||||
|
return new Gson().fromJson(je, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class AuthToken {
|
||||||
|
public String tokenType;
|
||||||
|
public Integer expiresIn;
|
||||||
|
public String accessToken;
|
||||||
|
public String refreshToken;
|
||||||
|
public Long issued;
|
||||||
|
|
||||||
|
public AuthToken() {
|
||||||
|
super();
|
||||||
|
issued = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Controller {
|
||||||
|
public Integer id;
|
||||||
|
public String name;
|
||||||
|
public ControllerStatus status;
|
||||||
|
public Location location;
|
||||||
|
public List<Zone> zones = null;
|
||||||
|
public List<Sensor> sensors = null;
|
||||||
|
public List<Forecast> forecast = null;
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ControllerStatus {
|
||||||
|
public Integer id;
|
||||||
|
public String name;
|
||||||
|
public String summary;
|
||||||
|
public Boolean online;
|
||||||
|
public Time lastContact;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Coordinates {
|
||||||
|
public Double latitude;
|
||||||
|
public Double longitude;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Customer {
|
||||||
|
public String email;
|
||||||
|
public String lastContact;
|
||||||
|
public List<Controller> controllers = null;
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Data {
|
||||||
|
public Customer me;
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Forecast {
|
||||||
|
public String time;
|
||||||
|
public String updateTime;
|
||||||
|
public String conditions;
|
||||||
|
public UnitValue highTemperature;
|
||||||
|
public UnitValue lowTemperature;
|
||||||
|
public UnitValue evapotranspiration;
|
||||||
|
public Integer probabilityOfPrecipitation;
|
||||||
|
public UnitValue precipitation;
|
||||||
|
public Number averageHumidity;
|
||||||
|
public UnitValue averageWindSpeed;
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Icon {
|
||||||
|
public Integer id;
|
||||||
|
public String fileName;
|
||||||
|
public Object customImage;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Input {
|
||||||
|
public Integer number;
|
||||||
|
public String label;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Location {
|
||||||
|
public Coordinates coordinates;
|
||||||
|
public List<Forecast> forecast;
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Mutation {
|
||||||
|
private static final String MUTATION_TEMPLATE = "mutation { %s }";
|
||||||
|
|
||||||
|
public String query;
|
||||||
|
|
||||||
|
public Mutation(String graphQLquery) {
|
||||||
|
this.query = String.format(MUTATION_TEMPLATE, graphQLquery);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class MutationResponse {
|
||||||
|
public Map<String, MutationResponseStatus> data;
|
||||||
|
|
||||||
|
public class MutationResponseStatus {
|
||||||
|
public StatusCode status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StatusCode {
|
||||||
|
OK,
|
||||||
|
WARNING,
|
||||||
|
ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class PastRuns {
|
||||||
|
public ZoneRun lastRun;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class QueryRequest {
|
||||||
|
public String query;
|
||||||
|
|
||||||
|
public QueryRequest(String query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class QueryResponse {
|
||||||
|
public Data data;
|
||||||
|
public List<QueryResponseError> errors;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class QueryResponseError {
|
||||||
|
public String message;
|
||||||
|
public QueryResponseErrorExtensions extentions;
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class QueryResponseErrorExtensions {
|
||||||
|
public String category;
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ScheduledRuns {
|
||||||
|
public String summary;
|
||||||
|
public ZoneRun nextRun;
|
||||||
|
public ZoneRun currentRun;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Sensor {
|
||||||
|
public Integer id;
|
||||||
|
public String name;
|
||||||
|
public Input input;
|
||||||
|
public SensorStatus status;
|
||||||
|
public SensorModel model;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class SensorModel {
|
||||||
|
public String modeType;
|
||||||
|
public Boolean active;
|
||||||
|
public Integer offLevel;
|
||||||
|
public Integer offTimer;
|
||||||
|
public Integer delay;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class SensorStatus {
|
||||||
|
public Boolean active;
|
||||||
|
public UnitValue waterFlow;
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Time {
|
||||||
|
public Integer timestamp;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class UnitValue {
|
||||||
|
public Number value;
|
||||||
|
public String unit;
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Zone {
|
||||||
|
public Integer id;
|
||||||
|
public String name;
|
||||||
|
public ZoneStatus status;
|
||||||
|
public Icon icon;
|
||||||
|
public ZoneNumber number;
|
||||||
|
public ScheduledRuns scheduledRuns;
|
||||||
|
public PastRuns pastRuns;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ZoneNumber {
|
||||||
|
public Integer value;
|
||||||
|
public String label;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ZoneRun {
|
||||||
|
public String id;
|
||||||
|
public Time startTime;
|
||||||
|
public Time endTime;
|
||||||
|
public Integer duration;
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.api.graphql.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ZoneStatus {
|
||||||
|
public Time suspendedUntil;
|
||||||
|
}
|
||||||
@ -10,22 +10,25 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api;
|
package org.openhab.binding.hydrawise.internal.api.local;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.util.BasicAuthentication;
|
import org.eclipse.jetty.client.util.BasicAuthentication;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.LocalScheduleResponse;
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.SetZoneResponse;
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseCommandException;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.local.dto.LocalScheduleResponse;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.local.dto.SetZoneResponse;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -92,11 +95,12 @@ public class HydrawiseLocalApiClient {
|
|||||||
* @throws HydrawiseConnectionException
|
* @throws HydrawiseConnectionException
|
||||||
* @throws HydrawiseAuthenticationException
|
* @throws HydrawiseAuthenticationException
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public LocalScheduleResponse getLocalSchedule()
|
public LocalScheduleResponse getLocalSchedule()
|
||||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
||||||
String json = doGet(localGetURL);
|
String json = doGet(localGetURL);
|
||||||
LocalScheduleResponse response = gson.fromJson(json, LocalScheduleResponse.class);
|
LocalScheduleResponse response = gson.fromJson(json, LocalScheduleResponse.class);
|
||||||
return Objects.requireNonNull(response);
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -10,7 +10,9 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api;
|
package org.openhab.binding.hydrawise.internal.api.local;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link HydrawiseZoneCommandBuilder} class builds a command URL string to use when sending commands to the
|
* The {@link HydrawiseZoneCommandBuilder} class builds a command URL string to use when sending commands to the
|
||||||
@ -19,6 +21,7 @@ package org.openhab.binding.hydrawise.internal.api;
|
|||||||
* @author Dan Cunningham - Initial contribution
|
* @author Dan Cunningham - Initial contribution
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
class HydrawiseZoneCommandBuilder {
|
class HydrawiseZoneCommandBuilder {
|
||||||
|
|
||||||
private final StringBuilder builder;
|
private final StringBuilder builder;
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -59,7 +59,5 @@ public class Controller {
|
|||||||
|
|
||||||
public String statusIcon;
|
public String statusIcon;
|
||||||
|
|
||||||
public Boolean online;
|
|
||||||
|
|
||||||
public List<String> tags = null;
|
public List<String> tags = null;
|
||||||
}
|
}
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Forecast} class models a daily weather forecast
|
* The {@link Forecast} class models a daily weather forecast
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -22,9 +22,9 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class LocalScheduleResponse extends Response {
|
public class LocalScheduleResponse extends Response {
|
||||||
|
|
||||||
public List<Running> running = new LinkedList<>();
|
public List<Running> running = new LinkedList<Running>();
|
||||||
|
|
||||||
public List<Relay> relays = new LinkedList<>();
|
public List<Relay> relays = new LinkedList<Relay>();
|
||||||
|
|
||||||
public String name;
|
public String name;
|
||||||
|
|
||||||
@ -10,7 +10,9 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link PlanArray} class models am account plan.
|
* The {@link PlanArray} class models am account plan.
|
||||||
@ -63,7 +65,8 @@ public class PlanArray {
|
|||||||
|
|
||||||
public String filetypeall;
|
public String filetypeall;
|
||||||
|
|
||||||
public String plan_type;
|
@SerializedName(value = "plan_type")
|
||||||
|
public String planType;
|
||||||
|
|
||||||
public String pushNotification;
|
public String pushNotification;
|
||||||
|
|
||||||
@ -10,9 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Relay} class models the Relay response message
|
* The {@link Relay} class models the Relay response message
|
||||||
@ -23,27 +21,19 @@ public class Relay {
|
|||||||
|
|
||||||
public Integer relayId;
|
public Integer relayId;
|
||||||
|
|
||||||
public Integer relay;
|
|
||||||
|
|
||||||
public String name;
|
|
||||||
|
|
||||||
public String icon;
|
|
||||||
|
|
||||||
public String lastwater;
|
|
||||||
|
|
||||||
public Integer time;
|
public Integer time;
|
||||||
|
|
||||||
public Integer type;
|
public Integer type;
|
||||||
|
|
||||||
@SerializedName("run")
|
public Integer relay;
|
||||||
public String runTime;
|
|
||||||
|
|
||||||
@SerializedName("run_seconds")
|
public String name;
|
||||||
public Integer runTimeSeconds;
|
|
||||||
|
|
||||||
public String nicetime;
|
public Integer frequency;
|
||||||
|
|
||||||
public String id;
|
public String timestr;
|
||||||
|
|
||||||
|
public Integer runSeconds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns back the actual relay number when multiple controllers are chained.
|
* Returns back the actual relay number when multiple controllers are chained.
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Response} class models Response messages
|
* The {@link Response} class models Response messages
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Running} class models a running relay
|
* The {@link Running} class models a running relay
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link SetControllerResponse} class models the SetController response message
|
* The {@link SetControllerResponse} class models the SetController response message
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link SetZoneResponse} class models the SetZone response message
|
* The {@link SetZoneResponse} class models the SetZone response message
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal.api.model;
|
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -30,7 +30,7 @@ public class StatusScheduleResponse extends LocalScheduleResponse {
|
|||||||
|
|
||||||
public Integer nextpoll;
|
public Integer nextpoll;
|
||||||
|
|
||||||
public List<Sensor> sensors = new LinkedList<>();
|
public List<Sensor> sensors = new LinkedList<Sensor>();
|
||||||
|
|
||||||
public String message;
|
public String message;
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ public class StatusScheduleResponse extends LocalScheduleResponse {
|
|||||||
|
|
||||||
public String lastContact;
|
public String lastContact;
|
||||||
|
|
||||||
public List<Forecast> forecast = new LinkedList<>();
|
public List<Forecast> forecast = new LinkedList<Forecast>();
|
||||||
|
|
||||||
public String status;
|
public String status;
|
||||||
|
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.config;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link HydrawiseAccountConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class HydrawiseAccountConfiguration {
|
||||||
|
public String userName = "";
|
||||||
|
public String password = "";
|
||||||
|
public Boolean savePassword = false;
|
||||||
|
public Integer refreshInterval = 60;
|
||||||
|
}
|
||||||
@ -10,27 +10,19 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal;
|
package org.openhab.binding.hydrawise.internal.config;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link HydrawiseCloudConfiguration} class contains fields mapping thing configuration parameters.
|
* The {@link HydrawiseControllerConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
*
|
*
|
||||||
* @author Dan Cunningham - Initial contribution
|
* @author Dan Cunningham - Initial contribution
|
||||||
*/
|
*/
|
||||||
public class HydrawiseCloudConfiguration {
|
@NonNullByDefault
|
||||||
|
public class HydrawiseControllerConfiguration {
|
||||||
/**
|
|
||||||
* Customer API key {@link https://app.hydrawise.com/config/account}
|
|
||||||
*/
|
|
||||||
public String apiKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* refresh interval in seconds.
|
|
||||||
*/
|
|
||||||
public Integer refresh;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* optional id of the controller to connect to
|
* optional id of the controller to connect to
|
||||||
*/
|
*/
|
||||||
public Integer controllerId;
|
public Integer controllerId = -1;
|
||||||
}
|
}
|
||||||
@ -10,30 +10,31 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal;
|
package org.openhab.binding.hydrawise.internal.config;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link HydrawiseLocalConfiguration} class contains fields mapping thing configuration parameters.
|
* The {@link HydrawiseLocalConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
*
|
*
|
||||||
* @author Dan Cunningham - Initial contribution
|
* @author Dan Cunningham - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class HydrawiseLocalConfiguration {
|
public class HydrawiseLocalConfiguration {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Host or IP for local controller
|
* Host or IP for local controller
|
||||||
*/
|
*/
|
||||||
public String host;
|
public String host = "";
|
||||||
/**
|
/**
|
||||||
* User name (admin) for local controller
|
* User name (admin) for local controller
|
||||||
*/
|
*/
|
||||||
public String username;
|
public String username = "";
|
||||||
/**
|
/**
|
||||||
* Password for local controller
|
* Password for local controller
|
||||||
*/
|
*/
|
||||||
public String password;
|
public String password = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* refresh interval in seconds.
|
* refresh interval in seconds.
|
||||||
*/
|
*/
|
||||||
public int refresh;
|
public int refresh = 30;
|
||||||
}
|
}
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.discovery;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants;
|
||||||
|
import org.openhab.binding.hydrawise.internal.HydrawiseControllerListener;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Controller;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Customer;
|
||||||
|
import org.openhab.binding.hydrawise.internal.handler.HydrawiseAccountHandler;
|
||||||
|
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||||
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
|
import org.openhab.core.thing.ThingUID;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(service = ThingHandlerService.class)
|
||||||
|
public class HydrawiseCloudControllerDiscoveryService extends AbstractDiscoveryService
|
||||||
|
implements HydrawiseControllerListener, ThingHandlerService {
|
||||||
|
|
||||||
|
private static final int TIMEOUT = 5;
|
||||||
|
@Nullable
|
||||||
|
HydrawiseAccountHandler handler;
|
||||||
|
|
||||||
|
public HydrawiseCloudControllerDiscoveryService() {
|
||||||
|
super(Collections.singleton(HydrawiseBindingConstants.THING_TYPE_CONTROLLER), TIMEOUT, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startScan() {
|
||||||
|
HydrawiseAccountHandler localHandler = this.handler;
|
||||||
|
if (localHandler != null) {
|
||||||
|
Customer data = localHandler.lastData();
|
||||||
|
if (data != null) {
|
||||||
|
data.controllers.forEach(controller -> addDiscoveryResults(controller));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deactivate() {
|
||||||
|
HydrawiseAccountHandler localHandler = this.handler;
|
||||||
|
if (localHandler != null) {
|
||||||
|
removeOlderResults(new Date().getTime(), localHandler.getThing().getUID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected synchronized void stopScan() {
|
||||||
|
super.stopScan();
|
||||||
|
HydrawiseAccountHandler localHandler = this.handler;
|
||||||
|
if (localHandler != null) {
|
||||||
|
removeOlderResults(getTimestampOfLastScan(), localHandler.getThing().getUID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onData(List<Controller> controllers) {
|
||||||
|
controllers.forEach(controller -> addDiscoveryResults(controller));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setThingHandler(ThingHandler handler) {
|
||||||
|
this.handler = (HydrawiseAccountHandler) handler;
|
||||||
|
this.handler.addControllerListeners(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ThingHandler getThingHandler() {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDiscoveryResults(Controller controller) {
|
||||||
|
HydrawiseAccountHandler localHandler = this.handler;
|
||||||
|
if (localHandler != null) {
|
||||||
|
String label = String.format("Hydrawise Controller %s", controller.name);
|
||||||
|
int id = controller.id;
|
||||||
|
ThingUID bridgeUID = localHandler.getThing().getUID();
|
||||||
|
ThingUID thingUID = new ThingUID(HydrawiseBindingConstants.THING_TYPE_CONTROLLER, bridgeUID,
|
||||||
|
String.valueOf(id));
|
||||||
|
thingDiscovered(DiscoveryResultBuilder.create(thingUID).withLabel(label).withBridge(bridgeUID)
|
||||||
|
.withProperty(HydrawiseBindingConstants.CONFIG_CONTROLLER_ID, id)
|
||||||
|
.withRepresentationProperty(String.valueOf(HydrawiseBindingConstants.CONFIG_CONTROLLER_ID))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,216 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.handler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.openhab.binding.hydrawise.internal.HydrawiseControllerListener;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.HydrawiseGraphQLClient;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Customer;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.QueryResponse;
|
||||||
|
import org.openhab.binding.hydrawise.internal.config.HydrawiseAccountConfiguration;
|
||||||
|
import org.openhab.binding.hydrawise.internal.discovery.HydrawiseCloudControllerDiscoveryService;
|
||||||
|
import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
|
||||||
|
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||||
|
import org.openhab.core.auth.client.oauth2.OAuthClientService;
|
||||||
|
import org.openhab.core.auth.client.oauth2.OAuthException;
|
||||||
|
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||||
|
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
||||||
|
import org.openhab.core.config.core.Configuration;
|
||||||
|
import org.openhab.core.thing.Bridge;
|
||||||
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.ThingStatus;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link HydrawiseAccountHandler} is responsible for handling for connecting to a Hydrawise account and polling for
|
||||||
|
* controller data
|
||||||
|
*
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class HydrawiseAccountHandler extends BaseBridgeHandler implements AccessTokenRefreshListener {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(HydrawiseAccountHandler.class);
|
||||||
|
/**
|
||||||
|
* Minimum amount of time we can poll for updates
|
||||||
|
*/
|
||||||
|
private static final int MIN_REFRESH_SECONDS = 30;
|
||||||
|
private static final String BASE_URL = "https://app.hydrawise.com/api/v2/";
|
||||||
|
private static final String AUTH_URL = BASE_URL + "oauth/access-token";
|
||||||
|
private static final String CLIENT_SECRET = "zn3CrjglwNV1";
|
||||||
|
private static final String CLIENT_ID = "hydrawise_app";
|
||||||
|
private static final String SCOPE = "all";
|
||||||
|
private final List<HydrawiseControllerListener> controllerListeners = new ArrayList<HydrawiseControllerListener>();
|
||||||
|
private final HydrawiseGraphQLClient apiClient;
|
||||||
|
private final OAuthClientService oAuthService;
|
||||||
|
private @Nullable ScheduledFuture<?> pollFuture;
|
||||||
|
private @Nullable Customer lastData;
|
||||||
|
private int refresh;
|
||||||
|
|
||||||
|
public HydrawiseAccountHandler(final Bridge bridge, final HttpClient httpClient, final OAuthFactory oAuthFactory) {
|
||||||
|
super(bridge);
|
||||||
|
this.oAuthService = oAuthFactory.createOAuthClientService(getThing().toString(), AUTH_URL, AUTH_URL, CLIENT_ID,
|
||||||
|
CLIENT_SECRET, SCOPE, false);
|
||||||
|
oAuthService.addAccessTokenRefreshListener(this);
|
||||||
|
this.apiClient = new HydrawiseGraphQLClient(httpClient, oAuthService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
logger.debug("Handler initialized.");
|
||||||
|
scheduler.schedule(this::configure, 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
logger.debug("Handler disposed.");
|
||||||
|
clearPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccessTokenResponse(AccessTokenResponse tokenResponse) {
|
||||||
|
logger.debug("Auth Token Refreshed, expires in {}", tokenResponse.getExpiresIn());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||||
|
return Collections.singleton(HydrawiseCloudControllerDiscoveryService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addControllerListeners(HydrawiseControllerListener listener) {
|
||||||
|
this.controllerListeners.add(listener);
|
||||||
|
Customer data = lastData;
|
||||||
|
if (data != null) {
|
||||||
|
listener.onData(data.controllers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeControllerListeners(HydrawiseControllerListener listener) {
|
||||||
|
this.controllerListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable HydrawiseGraphQLClient graphQLClient() {
|
||||||
|
return apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Customer lastData() {
|
||||||
|
return lastData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshData(int delaySeconds) {
|
||||||
|
initPolling(delaySeconds, this.refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configure() {
|
||||||
|
HydrawiseAccountConfiguration config = getConfig().as(HydrawiseAccountConfiguration.class);
|
||||||
|
try {
|
||||||
|
if (!config.userName.isEmpty() && !config.password.isEmpty()) {
|
||||||
|
if (!config.savePassword) {
|
||||||
|
Configuration editedConfig = editConfiguration();
|
||||||
|
editedConfig.remove("password");
|
||||||
|
updateConfiguration(editedConfig);
|
||||||
|
}
|
||||||
|
oAuthService.getAccessTokenByResourceOwnerPasswordCredentials(config.userName, config.password, SCOPE);
|
||||||
|
} else if (oAuthService.getAccessTokenResponse() == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Login credentials required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.refresh = Math.max(config.refreshInterval, MIN_REFRESH_SECONDS);
|
||||||
|
initPolling(0, refresh);
|
||||||
|
} catch (OAuthException | IOException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
} catch (OAuthResponseException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Login credentials required.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts/Restarts polling with an initial delay. This allows changes in the poll cycle for when commands are sent
|
||||||
|
* and we need to poll sooner then the next refresh cycle.
|
||||||
|
*/
|
||||||
|
private synchronized void initPolling(int initalDelay, int refresh) {
|
||||||
|
clearPolling();
|
||||||
|
pollFuture = scheduler.scheduleWithFixedDelay(this::poll, initalDelay, refresh, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops/clears this thing's polling future
|
||||||
|
*/
|
||||||
|
private void clearPolling() {
|
||||||
|
ScheduledFuture<?> localFuture = pollFuture;
|
||||||
|
if (isFutureValid(localFuture)) {
|
||||||
|
if (localFuture != null) {
|
||||||
|
localFuture.cancel(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFutureValid(@Nullable ScheduledFuture<?> future) {
|
||||||
|
return future != null && !future.isCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void poll() {
|
||||||
|
poll(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void poll(boolean retry) {
|
||||||
|
try {
|
||||||
|
QueryResponse response = apiClient.queryControllers();
|
||||||
|
if (response == null) {
|
||||||
|
throw new HydrawiseConnectionException("Malformed response");
|
||||||
|
}
|
||||||
|
if (response.errors != null && response.errors.size() > 0) {
|
||||||
|
throw new HydrawiseConnectionException(response.errors.stream().map(error -> error.message).reduce("",
|
||||||
|
(messages, message) -> messages + message + ". "));
|
||||||
|
}
|
||||||
|
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
}
|
||||||
|
lastData = response.data.me;
|
||||||
|
controllerListeners.forEach(listener -> {
|
||||||
|
listener.onData(response.data.me.controllers);
|
||||||
|
});
|
||||||
|
} catch (HydrawiseConnectionException e) {
|
||||||
|
if (retry) {
|
||||||
|
logger.debug("Retrying failed poll", e);
|
||||||
|
poll(false);
|
||||||
|
} else {
|
||||||
|
logger.debug("Will try again during next poll period", e);
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
} catch (HydrawiseAuthenticationException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||||
|
clearPolling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,436 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.hydrawise.internal.handler;
|
||||||
|
|
||||||
|
import static org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants.*;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import javax.measure.quantity.Speed;
|
||||||
|
import javax.measure.quantity.Temperature;
|
||||||
|
import javax.measure.quantity.Volume;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hydrawise.internal.HydrawiseControllerListener;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseCommandException;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.HydrawiseGraphQLClient;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Controller;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Forecast;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Sensor;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.UnitValue;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.Zone;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.graphql.dto.ZoneRun;
|
||||||
|
import org.openhab.binding.hydrawise.internal.config.HydrawiseControllerConfiguration;
|
||||||
|
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.library.unit.ImperialUnits;
|
||||||
|
import org.openhab.core.library.unit.SIUnits;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
|
import org.openhab.core.thing.Bridge;
|
||||||
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingStatus;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.BridgeHandler;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.RefreshType;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link HydrawiseControllerHandler} is responsible for handling commands, which are
|
||||||
|
* sent to one of the channels.
|
||||||
|
*
|
||||||
|
* @author Dan Cunningham - Initial contribution
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
|
public class HydrawiseControllerHandler extends BaseThingHandler implements HydrawiseControllerListener {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(HydrawiseControllerHandler.class);
|
||||||
|
private static final int DEFAULT_SUSPEND_TIME_HOURS = 24;
|
||||||
|
private static final int DEFAULT_REFRESH_SECONDS = 15;
|
||||||
|
// All responses use US local time formats
|
||||||
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM uu HH:mm:ss Z",
|
||||||
|
Locale.US);
|
||||||
|
private final Map<String, @Nullable State> stateMap = Collections
|
||||||
|
.synchronizedMap(new HashMap<String, @Nullable State>());
|
||||||
|
private final Map<String, @Nullable Zone> zoneMaps = Collections
|
||||||
|
.synchronizedMap(new HashMap<String, @Nullable Zone>());
|
||||||
|
private int controllerId;
|
||||||
|
|
||||||
|
public HydrawiseControllerHandler(Thing thing) {
|
||||||
|
super(thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
HydrawiseControllerConfiguration config = getConfigAs(HydrawiseControllerConfiguration.class);
|
||||||
|
controllerId = config.controllerId;
|
||||||
|
Bridge bridge = getBridge();
|
||||||
|
if (bridge != null) {
|
||||||
|
HydrawiseAccountHandler handler = (HydrawiseAccountHandler) bridge.getHandler();
|
||||||
|
if (handler != null) {
|
||||||
|
handler.addControllerListeners(this);
|
||||||
|
if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
logger.debug("handleCommand channel {} Command {}", channelUID.getAsString(), command.toFullString());
|
||||||
|
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||||
|
logger.debug("Controller is NOT ONLINE and is not responding to commands");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove our cached state for this, will be safely updated on next poll
|
||||||
|
stateMap.remove(channelUID.getAsString());
|
||||||
|
|
||||||
|
if (command instanceof RefreshType) {
|
||||||
|
// we already removed this from the cache
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HydrawiseGraphQLClient client = apiClient();
|
||||||
|
if (client == null) {
|
||||||
|
logger.debug("API client not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String group = channelUID.getGroupId();
|
||||||
|
String channelId = channelUID.getIdWithoutGroup();
|
||||||
|
boolean allCommand = CHANNEL_GROUP_ALLZONES.equals(group);
|
||||||
|
Zone zone = zoneMaps.get(group);
|
||||||
|
|
||||||
|
if (!allCommand && zone == null) {
|
||||||
|
logger.debug("Zone not found {}", group);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (channelId) {
|
||||||
|
case CHANNEL_ZONE_RUN_CUSTOM:
|
||||||
|
if (!(command instanceof QuantityType<?>)) {
|
||||||
|
logger.warn("Invalid command type for run custom {}", command.getClass().getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QuantityType<?> time = ((QuantityType<?>) command).toUnit(Units.SECOND);
|
||||||
|
|
||||||
|
if (time == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allCommand) {
|
||||||
|
client.runAllRelays(controllerId, time.intValue());
|
||||||
|
} else if (zone != null) {
|
||||||
|
client.runRelay(zone.id, time.intValue());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CHANNEL_ZONE_RUN:
|
||||||
|
if (!(command instanceof OnOffType)) {
|
||||||
|
logger.warn("Invalid command type for run {}", command.getClass().getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (allCommand) {
|
||||||
|
if (command == OnOffType.ON) {
|
||||||
|
client.runAllRelays(controllerId);
|
||||||
|
} else {
|
||||||
|
client.stopAllRelays(controllerId);
|
||||||
|
}
|
||||||
|
} else if (zone != null) {
|
||||||
|
if (command == OnOffType.ON) {
|
||||||
|
client.runRelay(zone.id);
|
||||||
|
} else {
|
||||||
|
client.stopRelay(zone.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CHANNEL_ZONE_SUSPEND:
|
||||||
|
if (!(command instanceof OnOffType)) {
|
||||||
|
logger.warn("Invalid command type for suspend {}", command.getClass().getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (allCommand) {
|
||||||
|
if (command == OnOffType.ON) {
|
||||||
|
client.suspendAllRelays(controllerId, OffsetDateTime.now(ZoneOffset.UTC)
|
||||||
|
.plus(DEFAULT_SUSPEND_TIME_HOURS, ChronoUnit.HOURS).format(DATE_FORMATTER));
|
||||||
|
} else {
|
||||||
|
client.resumeAllRelays(controllerId);
|
||||||
|
}
|
||||||
|
} else if (zone != null) {
|
||||||
|
if (command == OnOffType.ON) {
|
||||||
|
client.suspendRelay(zone.id, OffsetDateTime.now(ZoneOffset.UTC)
|
||||||
|
.plus(DEFAULT_SUSPEND_TIME_HOURS, ChronoUnit.HOURS).format(DATE_FORMATTER));
|
||||||
|
} else {
|
||||||
|
client.resumeRelay(zone.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CHANNEL_ZONE_SUSPENDUNTIL:
|
||||||
|
if (!(command instanceof DateTimeType)) {
|
||||||
|
logger.warn("Invalid command type for suspend {}", command.getClass().getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (allCommand) {
|
||||||
|
client.suspendAllRelays(controllerId,
|
||||||
|
((DateTimeType) command).getZonedDateTime().format(DATE_FORMATTER));
|
||||||
|
} else if (zone != null) {
|
||||||
|
client.suspendRelay(zone.id,
|
||||||
|
((DateTimeType) command).getZonedDateTime().format(DATE_FORMATTER));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.warn("Uknown channelId {}", channelId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HydrawiseAccountHandler handler = getAccountHandler();
|
||||||
|
if (handler != null) {
|
||||||
|
handler.refreshData(DEFAULT_REFRESH_SECONDS);
|
||||||
|
}
|
||||||
|
} catch (HydrawiseCommandException | HydrawiseConnectionException e) {
|
||||||
|
logger.debug("Could not issue command", e);
|
||||||
|
} catch (HydrawiseAuthenticationException e) {
|
||||||
|
logger.debug("Credentials not valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onData(List<Controller> controllers) {
|
||||||
|
logger.trace("onData my controller id {}", controllerId);
|
||||||
|
controllers.stream().filter(c -> c.id == controllerId).findFirst().ifPresent(controller -> {
|
||||||
|
logger.trace("Updating Controller {} sensors {} forecast {} ", controller.id, controller.sensors,
|
||||||
|
controller.location.forecast);
|
||||||
|
updateController(controller);
|
||||||
|
if (controller.sensors != null) {
|
||||||
|
updateSensors(controller.sensors);
|
||||||
|
}
|
||||||
|
if (controller.location != null && controller.location.forecast != null) {
|
||||||
|
updateForecast(controller.location.forecast);
|
||||||
|
}
|
||||||
|
if (controller.zones != null) {
|
||||||
|
updateZones(controller.zones);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update values with what the cloud tells us even though the controller may be offline
|
||||||
|
if (!controller.status.online) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
String.format("Controller Offline: %s last seen %s", controller.status.summary,
|
||||||
|
secondsToDateTime(controller.status.lastContact.timestamp)));
|
||||||
|
} else if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelLinked(ChannelUID channelUID) {
|
||||||
|
// clear our cached value so the new channel gets updated on the next poll
|
||||||
|
stateMap.remove(channelUID.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateController(Controller controller) {
|
||||||
|
updateGroupState(CHANNEL_GROUP_CONTROLLER_SYSTEM, CHANNEL_CONTROLLER_NAME, new StringType(controller.name));
|
||||||
|
updateGroupState(CHANNEL_GROUP_CONTROLLER_SYSTEM, CHANNEL_CONTROLLER_SUMMARY,
|
||||||
|
new StringType(controller.status.summary));
|
||||||
|
updateGroupState(CHANNEL_GROUP_CONTROLLER_SYSTEM, CHANNEL_CONTROLLER_LAST_CONTACT,
|
||||||
|
secondsToDateTime(controller.status.lastContact.timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateZones(List<Zone> zones) {
|
||||||
|
AtomicReference<Boolean> anyRunning = new AtomicReference<Boolean>(false);
|
||||||
|
AtomicReference<Boolean> anySuspended = new AtomicReference<Boolean>(false);
|
||||||
|
int i = 1;
|
||||||
|
for (Zone zone : zones) {
|
||||||
|
String group = "zone" + (i++);
|
||||||
|
zoneMaps.put(group, zone);
|
||||||
|
logger.trace("Updateing Zone {} {} ", group, zone.name);
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_NAME, new StringType(zone.name));
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_ICON, new StringType(BASE_IMAGE_URL + zone.icon.fileName));
|
||||||
|
if (zone.scheduledRuns != null) {
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_SUMMARY,
|
||||||
|
zone.scheduledRuns.summary != null ? new StringType(zone.scheduledRuns.summary)
|
||||||
|
: UnDefType.UNDEF);
|
||||||
|
ZoneRun nextRun = zone.scheduledRuns.nextRun;
|
||||||
|
if (nextRun != null) {
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_DURATION, new QuantityType<>(nextRun.duration, Units.MINUTE));
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_NEXT_RUN_TIME_TIME,
|
||||||
|
secondsToDateTime(nextRun.startTime.timestamp));
|
||||||
|
} else {
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_DURATION, UnDefType.UNDEF);
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_NEXT_RUN_TIME_TIME, UnDefType.UNDEF);
|
||||||
|
}
|
||||||
|
ZoneRun currRunn = zone.scheduledRuns.currentRun;
|
||||||
|
if (currRunn != null) {
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_RUN, OnOffType.ON);
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_TIME_LEFT, new QuantityType<>(
|
||||||
|
currRunn.endTime.timestamp - Instant.now().getEpochSecond(), Units.SECOND));
|
||||||
|
anyRunning.set(true);
|
||||||
|
} else {
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_RUN, OnOffType.OFF);
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_TIME_LEFT, new QuantityType<>(0, Units.MINUTE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (zone.status.suspendedUntil != null) {
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_SUSPEND, OnOffType.ON);
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_SUSPENDUNTIL,
|
||||||
|
secondsToDateTime(zone.status.suspendedUntil.timestamp));
|
||||||
|
anySuspended.set(true);
|
||||||
|
} else {
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_SUSPEND, OnOffType.OFF);
|
||||||
|
updateGroupState(group, CHANNEL_ZONE_SUSPENDUNTIL, UnDefType.UNDEF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_RUN, anyRunning.get() ? OnOffType.ON : OnOffType.OFF);
|
||||||
|
updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_SUSPEND,
|
||||||
|
anySuspended.get() ? OnOffType.ON : OnOffType.OFF);
|
||||||
|
updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_SUSPENDUNTIL, UnDefType.UNDEF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSensors(List<Sensor> sensors) {
|
||||||
|
int i = 1;
|
||||||
|
for (Sensor sensor : sensors) {
|
||||||
|
String group = "sensor" + (i++);
|
||||||
|
updateGroupState(group, CHANNEL_SENSOR_NAME, new StringType(sensor.name));
|
||||||
|
if (sensor.model.offTimer != null) {
|
||||||
|
updateGroupState(group, CHANNEL_SENSOR_OFFTIMER,
|
||||||
|
new QuantityType<>(sensor.model.offTimer, Units.SECOND));
|
||||||
|
}
|
||||||
|
if (sensor.model.delay != null) {
|
||||||
|
updateGroupState(group, CHANNEL_SENSOR_DELAY, new QuantityType<>(sensor.model.delay, Units.SECOND));
|
||||||
|
}
|
||||||
|
if (sensor.model.offLevel != null) {
|
||||||
|
updateGroupState(group, CHANNEL_SENSOR_OFFLEVEL, new DecimalType(sensor.model.offLevel));
|
||||||
|
}
|
||||||
|
if (sensor.status.active != null) {
|
||||||
|
updateGroupState(group, CHANNEL_SENSOR_ACTIVE, sensor.status.active ? OnOffType.ON : OnOffType.OFF);
|
||||||
|
}
|
||||||
|
if (sensor.status.waterFlow != null) {
|
||||||
|
updateGroupState(group, CHANNEL_SENSOR_WATERFLOW,
|
||||||
|
waterFlowToQuantityType(sensor.status.waterFlow.value, sensor.status.waterFlow.unit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateForecast(List<Forecast> forecasts) {
|
||||||
|
int i = 1;
|
||||||
|
for (Forecast forecast : forecasts) {
|
||||||
|
String group = "forecast" + (i++);
|
||||||
|
updateGroupState(group, CHANNEL_FORECAST_TIME, stringToDateTime(forecast.time));
|
||||||
|
updateGroupState(group, CHANNEL_FORECAST_CONDITIONS, new StringType(forecast.conditions));
|
||||||
|
updateGroupState(group, CHANNEL_FORECAST_HUMIDITY, new DecimalType(forecast.averageHumidity.intValue()));
|
||||||
|
updateTemperature(forecast.highTemperature, group, CHANNEL_FORECAST_TEMPERATURE_HIGH);
|
||||||
|
updateTemperature(forecast.lowTemperature, group, CHANNEL_FORECAST_TEMPERATURE_LOW);
|
||||||
|
updateWindspeed(forecast.averageWindSpeed, group, CHANNEL_FORECAST_WIND);
|
||||||
|
// this seems to sometimes be optional
|
||||||
|
if (forecast.evapotranspiration != null) {
|
||||||
|
updateGroupState(group, CHANNEL_FORECAST_EVAPOTRANSPRIATION,
|
||||||
|
new DecimalType(forecast.evapotranspiration.value.floatValue()));
|
||||||
|
}
|
||||||
|
updateGroupState(group, CHANNEL_FORECAST_PRECIPITATION,
|
||||||
|
new DecimalType(forecast.precipitation.value.floatValue()));
|
||||||
|
updateGroupState(group, CHANNEL_FORECAST_PROBABILITYOFPRECIPITATION,
|
||||||
|
new DecimalType(forecast.probabilityOfPrecipitation));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTemperature(UnitValue temperature, String group, String channel) {
|
||||||
|
logger.debug("TEMP {} {} {} {}", group, channel, temperature.unit, temperature.value);
|
||||||
|
updateGroupState(group, channel, new QuantityType<Temperature>(temperature.value,
|
||||||
|
"\\u00b0F".equals(temperature.unit) ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateWindspeed(UnitValue wind, String group, String channel) {
|
||||||
|
updateGroupState(group, channel, new QuantityType<Speed>(wind.value,
|
||||||
|
"mph".equals(wind.unit) ? ImperialUnits.MILES_PER_HOUR : SIUnits.KILOMETRE_PER_HOUR));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateGroupState(String group, String channelID, State state) {
|
||||||
|
String channelName = group + "#" + channelID;
|
||||||
|
State oldState = stateMap.put(channelName, state);
|
||||||
|
if (!state.equals(oldState)) {
|
||||||
|
ChannelUID channelUID = new ChannelUID(this.getThing().getUID(), channelName);
|
||||||
|
logger.debug("updateState updating {} {}", channelUID, state);
|
||||||
|
updateState(channelUID, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private HydrawiseAccountHandler getAccountHandler() {
|
||||||
|
Bridge bridge = getBridge();
|
||||||
|
if (bridge == null) {
|
||||||
|
logger.warn("No bridge found for thing");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BridgeHandler handler = bridge.getHandler();
|
||||||
|
if (handler == null) {
|
||||||
|
logger.warn("No handler found for bridge");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ((HydrawiseAccountHandler) handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private HydrawiseGraphQLClient apiClient() {
|
||||||
|
HydrawiseAccountHandler handler = getAccountHandler();
|
||||||
|
if (handler == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return handler.graphQLClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTimeType secondsToDateTime(Integer seconds) {
|
||||||
|
Instant instant = Instant.ofEpochSecond(seconds);
|
||||||
|
ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);
|
||||||
|
return new DateTimeType(zdt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTimeType stringToDateTime(String date) {
|
||||||
|
ZonedDateTime zdt = ZonedDateTime.parse(date, DATE_FORMATTER);
|
||||||
|
return new DateTimeType(zdt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuantityType<Volume> waterFlowToQuantityType(Number flow, String units) {
|
||||||
|
double waterFlow = flow.doubleValue();
|
||||||
|
if ("gals".equals(units)) {
|
||||||
|
waterFlow = waterFlow * 3.785;
|
||||||
|
}
|
||||||
|
return new QuantityType<>(waterFlow, Units.LITRE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.hydrawise.internal;
|
package org.openhab.binding.hydrawise.internal.handler;
|
||||||
|
|
||||||
import static org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants.*;
|
import static org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants.*;
|
||||||
|
|
||||||
@ -19,23 +19,27 @@ import java.time.temporal.ChronoUnit;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseCommandException;
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseCommandException;
|
||||||
import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
|
import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.LocalScheduleResponse;
|
import org.openhab.binding.hydrawise.internal.api.local.HydrawiseLocalApiClient;
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.Relay;
|
import org.openhab.binding.hydrawise.internal.api.local.dto.LocalScheduleResponse;
|
||||||
import org.openhab.binding.hydrawise.internal.api.model.Running;
|
import org.openhab.binding.hydrawise.internal.api.local.dto.Relay;
|
||||||
|
import org.openhab.binding.hydrawise.internal.api.local.dto.Running;
|
||||||
|
import org.openhab.binding.hydrawise.internal.config.HydrawiseLocalConfiguration;
|
||||||
import org.openhab.core.library.types.DateTimeType;
|
import org.openhab.core.library.types.DateTimeType;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
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.types.StringType;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
@ -49,23 +53,22 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link HydrawiseHandler} is responsible for handling commands, which are
|
* The {@link HydrawiseLocalHandler} is responsible for handling commands, which are
|
||||||
* sent to one of the channels.
|
* sent to one of the channels.
|
||||||
*
|
*
|
||||||
* @author Dan Cunningham - Initial contribution
|
* @author Dan Cunningham - Initial contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public abstract class HydrawiseHandler extends BaseThingHandler {
|
public class HydrawiseLocalHandler extends BaseThingHandler {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(HydrawiseLocalHandler.class);
|
||||||
private final Logger logger = LoggerFactory.getLogger(HydrawiseHandler.class);
|
protected final Map<String, State> stateMap = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
protected final Map<String, Relay> relayMap = Collections.synchronizedMap(new HashMap<>());
|
||||||
private @Nullable ScheduledFuture<?> pollFuture;
|
private @Nullable ScheduledFuture<?> pollFuture;
|
||||||
private Map<String, State> stateMap = Collections.synchronizedMap(new HashMap<>());
|
|
||||||
private Map<String, Relay> relayMap = Collections.synchronizedMap(new HashMap<>());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* value observed being used by the Hydrawise clients as a max time value,
|
* value observed being used by the Hydrawise clients as a max time value,
|
||||||
*/
|
*/
|
||||||
private static long MAX_RUN_TIME = 157680000;
|
private static final long MAX_RUN_TIME = 157680000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum amount of time we can poll for updates
|
* Minimum amount of time we can poll for updates
|
||||||
@ -86,8 +89,28 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
|||||||
* Future to poll for updated
|
* Future to poll for updated
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public HydrawiseHandler(Thing thing) {
|
HydrawiseLocalApiClient client;
|
||||||
|
|
||||||
|
public HydrawiseLocalHandler(Thing thing, HttpClient httpClient) {
|
||||||
super(thing);
|
super(thing);
|
||||||
|
client = new HydrawiseLocalApiClient(httpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
scheduler.schedule(this::configureInternal, 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
logger.debug("Handler disposed.");
|
||||||
|
clearPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelLinked(ChannelUID channelUID) {
|
||||||
|
// clear our cached value so the new channel gets updated on the next poll
|
||||||
|
stateMap.remove(channelUID.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "null", "unused" }) // compiler does not like relayMap.get can return null
|
@SuppressWarnings({ "null", "unused" }) // compiler does not like relayMap.get can return null
|
||||||
@ -120,15 +143,14 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
|||||||
clearPolling();
|
clearPolling();
|
||||||
switch (channelId) {
|
switch (channelId) {
|
||||||
case CHANNEL_ZONE_RUN_CUSTOM:
|
case CHANNEL_ZONE_RUN_CUSTOM:
|
||||||
if (!(command instanceof DecimalType)) {
|
if (!(command instanceof QuantityType<?>)) {
|
||||||
logger.warn("Invalid command type for run custom {}", command.getClass().getName());
|
logger.warn("Invalid command type for run custom {}", command.getClass().getName());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (allCommand) {
|
if (allCommand) {
|
||||||
sendRunAllCommand(((DecimalType) command).intValue());
|
client.runAllRelays(((QuantityType<?>) command).intValue());
|
||||||
} else {
|
} else {
|
||||||
Objects.requireNonNull(relay);
|
client.runRelay(((QuantityType<?>) command).intValue(), relay.relay);
|
||||||
sendRunCommand(((DecimalType) command).intValue(), relay);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CHANNEL_ZONE_RUN:
|
case CHANNEL_ZONE_RUN:
|
||||||
@ -138,16 +160,15 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
|||||||
}
|
}
|
||||||
if (allCommand) {
|
if (allCommand) {
|
||||||
if (command == OnOffType.ON) {
|
if (command == OnOffType.ON) {
|
||||||
sendRunAllCommand();
|
client.runAllRelays();
|
||||||
} else {
|
} else {
|
||||||
sendStopAllCommand();
|
client.stopAllRelays();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Objects.requireNonNull(relay);
|
|
||||||
if (command == OnOffType.ON) {
|
if (command == OnOffType.ON) {
|
||||||
sendRunCommand(relay);
|
client.runRelay(relay.relay);
|
||||||
} else {
|
} else {
|
||||||
sendStopCommand(relay);
|
client.stopRelay(relay.relay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -163,46 +184,6 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
scheduler.schedule(this::configureInternal, 0, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose() {
|
|
||||||
logger.debug("Handler disposed.");
|
|
||||||
clearPolling();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelLinked(ChannelUID channelUID) {
|
|
||||||
// clear our cached value so the new channel gets updated on the next poll
|
|
||||||
stateMap.remove(channelUID.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void configure()
|
|
||||||
throws NotConfiguredException, HydrawiseConnectionException, HydrawiseAuthenticationException;
|
|
||||||
|
|
||||||
protected abstract void pollController() throws HydrawiseConnectionException, HydrawiseAuthenticationException;
|
|
||||||
|
|
||||||
protected abstract void sendRunCommand(int seconds, Relay relay)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException;
|
|
||||||
|
|
||||||
protected abstract void sendRunCommand(Relay relay)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException;
|
|
||||||
|
|
||||||
protected abstract void sendStopCommand(Relay relay)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException;
|
|
||||||
|
|
||||||
protected abstract void sendRunAllCommand()
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException;
|
|
||||||
|
|
||||||
protected abstract void sendRunAllCommand(int seconds)
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException;
|
|
||||||
|
|
||||||
protected abstract void sendStopAllCommand()
|
|
||||||
throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException;
|
|
||||||
|
|
||||||
protected void updateZones(LocalScheduleResponse status) {
|
protected void updateZones(LocalScheduleResponse status) {
|
||||||
ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS);
|
ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS);
|
||||||
status.relays.forEach(r -> {
|
status.relays.forEach(r -> {
|
||||||
@ -211,12 +192,8 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
|||||||
logger.trace("Updateing Zone {} {} ", group, r.name);
|
logger.trace("Updateing Zone {} {} ", group, r.name);
|
||||||
updateGroupState(group, CHANNEL_ZONE_NAME, new StringType(r.name));
|
updateGroupState(group, CHANNEL_ZONE_NAME, new StringType(r.name));
|
||||||
updateGroupState(group, CHANNEL_ZONE_TYPE, new DecimalType(r.type));
|
updateGroupState(group, CHANNEL_ZONE_TYPE, new DecimalType(r.type));
|
||||||
updateGroupState(group, CHANNEL_ZONE_TIME,
|
updateGroupState(group, CHANNEL_ZONE_STARTTIME,
|
||||||
r.runTimeSeconds != null ? new DecimalType(r.runTimeSeconds) : UnDefType.UNDEF);
|
r.runSeconds != null ? new QuantityType<>(r.runSeconds, Units.SECOND) : UnDefType.UNDEF);
|
||||||
String icon = r.icon;
|
|
||||||
if (icon != null && !icon.isBlank()) {
|
|
||||||
updateGroupState(group, CHANNEL_ZONE_ICON, new StringType(BASE_IMAGE_URL + icon));
|
|
||||||
}
|
|
||||||
if (r.time >= MAX_RUN_TIME) {
|
if (r.time >= MAX_RUN_TIME) {
|
||||||
updateGroupState(group, CHANNEL_ZONE_NEXT_RUN_TIME_TIME, UnDefType.UNDEF);
|
updateGroupState(group, CHANNEL_ZONE_NEXT_RUN_TIME_TIME, UnDefType.UNDEF);
|
||||||
} else {
|
} else {
|
||||||
@ -228,32 +205,21 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
|||||||
.filter(z -> Integer.parseInt(z.relayId) == r.relayId.intValue()).findAny();
|
.filter(z -> Integer.parseInt(z.relayId) == r.relayId.intValue()).findAny();
|
||||||
if (running.isPresent()) {
|
if (running.isPresent()) {
|
||||||
updateGroupState(group, CHANNEL_ZONE_RUN, OnOffType.ON);
|
updateGroupState(group, CHANNEL_ZONE_RUN, OnOffType.ON);
|
||||||
updateGroupState(group, CHANNEL_ZONE_TIME_LEFT, new DecimalType(running.get().timeLeft));
|
updateGroupState(group, CHANNEL_ZONE_TIME_LEFT,
|
||||||
|
new QuantityType<>(running.get().timeLeft, Units.SECOND));
|
||||||
logger.debug("{} Time Left {}", r.name, running.get().timeLeft);
|
logger.debug("{} Time Left {}", r.name, running.get().timeLeft);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
updateGroupState(group, CHANNEL_ZONE_RUN, OnOffType.OFF);
|
updateGroupState(group, CHANNEL_ZONE_RUN, OnOffType.OFF);
|
||||||
updateGroupState(group, CHANNEL_ZONE_TIME_LEFT, new DecimalType(0));
|
updateGroupState(group, CHANNEL_ZONE_TIME_LEFT, new QuantityType<>(0, Units.SECOND));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_RUN,
|
updateGroupState(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_RUN,
|
||||||
!status.running.isEmpty() ? OnOffType.ON : OnOffType.OFF);
|
status.running.size() > 0 ? OnOffType.ON : OnOffType.OFF);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateGroupState(String group, String channelID, State state) {
|
|
||||||
String channelName = group + "#" + channelID;
|
|
||||||
State oldState = stateMap.put(channelName, state);
|
|
||||||
if (!state.equals(oldState)) {
|
|
||||||
ChannelUID channelUID = new ChannelUID(this.getThing().getUID(), channelName);
|
|
||||||
logger.debug("updateState updating {} {}", channelUID, state);
|
|
||||||
updateState(channelUID, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
@NonNullByDefault
|
|
||||||
protected class NotConfiguredException extends Exception {
|
protected class NotConfiguredException extends Exception {
|
||||||
NotConfiguredException(String message) {
|
NotConfiguredException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
@ -269,11 +235,19 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
|||||||
stateMap.clear();
|
stateMap.clear();
|
||||||
relayMap.clear();
|
relayMap.clear();
|
||||||
try {
|
try {
|
||||||
configure();
|
HydrawiseLocalConfiguration configuration = getConfig().as(HydrawiseLocalConfiguration.class);
|
||||||
initPolling(0);
|
this.refresh = Math.max(configuration.refresh, MIN_REFRESH_SECONDS);
|
||||||
} catch (NotConfiguredException e) {
|
logger.trace("Connecting to host {}", configuration.host);
|
||||||
logger.debug("Configuration error {}", e.getMessage());
|
client.setCredentials(configuration.host, configuration.username, configuration.password);
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
LocalScheduleResponse response = client.getLocalSchedule();
|
||||||
|
if (response != null) {
|
||||||
|
updateZones(response);
|
||||||
|
initPolling(refresh);
|
||||||
|
} else {
|
||||||
|
logger.debug("Could not connect to service");
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"Invalid response from service");
|
||||||
|
}
|
||||||
} catch (HydrawiseConnectionException e) {
|
} catch (HydrawiseConnectionException e) {
|
||||||
logger.debug("Could not connect to service");
|
logger.debug("Could not connect to service");
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
@ -310,7 +284,10 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
|||||||
*/
|
*/
|
||||||
private void pollControllerInternal() {
|
private void pollControllerInternal() {
|
||||||
try {
|
try {
|
||||||
pollController();
|
LocalScheduleResponse response = client.getLocalSchedule();
|
||||||
|
if (response != null) {
|
||||||
|
updateZones(response);
|
||||||
|
}
|
||||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
}
|
}
|
||||||
@ -324,4 +301,14 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
|||||||
configureInternal();
|
configureInternal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateGroupState(String group, String channelID, State state) {
|
||||||
|
String channelName = group + "#" + channelID;
|
||||||
|
State oldState = stateMap.put(channelName, state);
|
||||||
|
if (!state.equals(oldState)) {
|
||||||
|
ChannelUID channelUID = new ChannelUID(this.getThing().getUID(), channelName);
|
||||||
|
logger.debug("updateState updating {} {}", channelUID, state);
|
||||||
|
updateState(channelUID, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -10,12 +10,14 @@
|
|||||||
<channels>
|
<channels>
|
||||||
<channel id="name" typeId="name"/>
|
<channel id="name" typeId="name"/>
|
||||||
<channel id="icon" typeId="icon"/>
|
<channel id="icon" typeId="icon"/>
|
||||||
<channel id="time" typeId="time"/>
|
|
||||||
<channel id="type" typeId="type"/>
|
<channel id="type" typeId="type"/>
|
||||||
<channel id="runcustom" typeId="runcustom"/>
|
|
||||||
<channel id="run" typeId="run"/>
|
<channel id="run" typeId="run"/>
|
||||||
|
<channel id="runcustom" typeId="runcustom"/>
|
||||||
<channel id="nextruntime" typeId="nextruntime"/>
|
<channel id="nextruntime" typeId="nextruntime"/>
|
||||||
|
<channel id="suspend" typeId="suspend"/>
|
||||||
|
<channel id="suspenduntil" typeId="suspenduntil"/>
|
||||||
<channel id="timeleft" typeId="timeleft"/>
|
<channel id="timeleft" typeId="timeleft"/>
|
||||||
|
<channel id="summary" typeId="summary"/>
|
||||||
</channels>
|
</channels>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|
||||||
@ -25,6 +27,8 @@
|
|||||||
<channels>
|
<channels>
|
||||||
<channel id="runcustom" typeId="runcustom"/>
|
<channel id="runcustom" typeId="runcustom"/>
|
||||||
<channel id="run" typeId="run"/>
|
<channel id="run" typeId="run"/>
|
||||||
|
<channel id="suspend" typeId="suspend"/>
|
||||||
|
<channel id="suspenduntil" typeId="suspenduntil"/>
|
||||||
</channels>
|
</channels>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|
||||||
@ -34,11 +38,11 @@
|
|||||||
<channels>
|
<channels>
|
||||||
<channel id="name" typeId="name"/>
|
<channel id="name" typeId="name"/>
|
||||||
<channel id="input" typeId="input"/>
|
<channel id="input" typeId="input"/>
|
||||||
<channel id="mode" typeId="mode"/>
|
<channel id="delay" typeId="delay"/>
|
||||||
<channel id="timer" typeId="timer"/>
|
|
||||||
<channel id="offtimer" typeId="offtimer"/>
|
<channel id="offtimer" typeId="offtimer"/>
|
||||||
<channel id="offlevel" typeId="offlevel"/>
|
<channel id="offlevel" typeId="offlevel"/>
|
||||||
<channel id="active" typeId="active"/>
|
<channel id="active" typeId="active"/>
|
||||||
|
<channel id="waterflow" typeId="waterflow"/>
|
||||||
</channels>
|
</channels>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|
||||||
@ -49,13 +53,43 @@
|
|||||||
<channel id="temperaturehigh" typeId="temperaturehigh"/>
|
<channel id="temperaturehigh" typeId="temperaturehigh"/>
|
||||||
<channel id="temperaturelow" typeId="temperaturelow"/>
|
<channel id="temperaturelow" typeId="temperaturelow"/>
|
||||||
<channel id="conditions" typeId="conditions"/>
|
<channel id="conditions" typeId="conditions"/>
|
||||||
<channel id="day" typeId="day"/>
|
<channel id="time" typeId="time"/>
|
||||||
<channel id="humidity" typeId="humidity"/>
|
<channel id="humidity" typeId="humidity"/>
|
||||||
<channel id="wind" typeId="wind"/>
|
<channel id="wind" typeId="wind"/>
|
||||||
|
<channel id="evapotranspiration" typeId="evapotranspiration"/>
|
||||||
|
<channel id="precipitation" typeId="precipitation"/>
|
||||||
|
<channel id="probabilityofprecipitation" typeId="probabilityofprecipitation"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
|
<channel-group-type id="system">
|
||||||
|
<label>System</label>
|
||||||
|
<description>Controller system data</description>
|
||||||
|
<channels>
|
||||||
|
<channel id="name" typeId="name"/>
|
||||||
|
<channel id="summary" typeId="summary"/>
|
||||||
|
<channel id="lastcontacttime" typeId="lastcontacttime"/>
|
||||||
</channels>
|
</channels>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|
||||||
<!-- Controller -->
|
<!-- Controller -->
|
||||||
|
|
||||||
|
<channel-type id="lastcontacttime" advanced="true">
|
||||||
|
<item-type>DateTime</item-type>
|
||||||
|
<label>Last Contact Time</label>
|
||||||
|
<description>Last contact time of a controller</description>
|
||||||
|
<state readOnly="true"></state>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="summary">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Status Summary</label>
|
||||||
|
<description>Status summary</description>
|
||||||
|
<state readOnly="true"></state>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<!-- Zones -->
|
||||||
|
|
||||||
<channel-type id="name">
|
<channel-type id="name">
|
||||||
<item-type>String</item-type>
|
<item-type>String</item-type>
|
||||||
<label>Name</label>
|
<label>Name</label>
|
||||||
@ -70,10 +104,17 @@
|
|||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="time" advanced="true">
|
<channel-type id="starttime" advanced="true">
|
||||||
<item-type>Number</item-type>
|
<item-type>DateTime</item-type>
|
||||||
<label>Start Time</label>
|
<label>Start Time</label>
|
||||||
<description>Zone start time in seconds</description>
|
<description>Next zone start time</description>
|
||||||
|
<state readOnly="true"></state>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="duration" advanced="true">
|
||||||
|
<item-type>Number:Time</item-type>
|
||||||
|
<label>Duration</label>
|
||||||
|
<description>Next start duration</description>
|
||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
@ -87,7 +128,7 @@
|
|||||||
<channel-type id="nextruntime">
|
<channel-type id="nextruntime">
|
||||||
<item-type>DateTime</item-type>
|
<item-type>DateTime</item-type>
|
||||||
<label>Next Run Time</label>
|
<label>Next Run Time</label>
|
||||||
<description>Next time this zone is scheduled to run</description>
|
<description>The next time this zone is scheduled to run</description>
|
||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
@ -98,13 +139,25 @@
|
|||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="runcustom">
|
<channel-type id="runcustom">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number:Time</item-type>
|
||||||
<label>Run Zones With Custom Duration </label>
|
<label>Run Zones With Custom Duration</label>
|
||||||
<description>Run zones now for a custom duration of time in seconds</description>
|
<description>Run zones now for a custom duration</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="suspend">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Suspend Zones</label>
|
||||||
|
<description>Suspends or resumes zones</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="suspenduntil">
|
||||||
|
<item-type>DateTime</item-type>
|
||||||
|
<label>Suspend Zones</label>
|
||||||
|
<description>Suspends zones until this date</description>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="timeleft">
|
<channel-type id="timeleft">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number:Time</item-type>
|
||||||
<label>Time Left Seconds</label>
|
<label>Time Left Seconds</label>
|
||||||
<description>Time left that zone will run for</description>
|
<description>Time left that zone will run for</description>
|
||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
@ -126,22 +179,15 @@
|
|||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="mode" advanced="true">
|
<channel-type id="delay" advanced="true">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number:Time</item-type>
|
||||||
<label>Mode</label>
|
<label>Delay</label>
|
||||||
<description>Sensor mode</description>
|
<description>Sensor delay</description>
|
||||||
<state readOnly="true"></state>
|
|
||||||
</channel-type>
|
|
||||||
|
|
||||||
<channel-type id="timer" advanced="true">
|
|
||||||
<item-type>Number</item-type>
|
|
||||||
<label>Timer</label>
|
|
||||||
<description>Sensor timer</description>
|
|
||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="offtimer" advanced="true">
|
<channel-type id="offtimer" advanced="true">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number:Time</item-type>
|
||||||
<label>Off Timer</label>
|
<label>Off Timer</label>
|
||||||
<description>Sensor off timer</description>
|
<description>Sensor off timer</description>
|
||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
@ -160,6 +206,7 @@
|
|||||||
<description>Sensor off level</description>
|
<description>Sensor off level</description>
|
||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="active">
|
<channel-type id="active">
|
||||||
<item-type>Switch</item-type>
|
<item-type>Switch</item-type>
|
||||||
<label>Active</label>
|
<label>Active</label>
|
||||||
@ -167,6 +214,13 @@
|
|||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="waterflow" advanced="true">
|
||||||
|
<item-type>Number:Volume</item-type>
|
||||||
|
<label>Water Flow</label>
|
||||||
|
<description>Sensor water flow</description>
|
||||||
|
<state readOnly="true"></state>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
<!-- Weather Forecast -->
|
<!-- Weather Forecast -->
|
||||||
<channel-type id="temperaturehigh">
|
<channel-type id="temperaturehigh">
|
||||||
<item-type>Number:Temperature</item-type>
|
<item-type>Number:Temperature</item-type>
|
||||||
@ -198,10 +252,10 @@
|
|||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="day">
|
<channel-type id="time">
|
||||||
<item-type>String</item-type>
|
<item-type>DateTime</item-type>
|
||||||
<label>Day of Week</label>
|
<label>Forecast Time</label>
|
||||||
<description>Day of week for the weather forecast</description>
|
<description>Forecast date and time</description>
|
||||||
<state readOnly="true"></state>
|
<state readOnly="true"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
@ -220,4 +274,25 @@
|
|||||||
<category>Wind</category>
|
<category>Wind</category>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="evapotranspiration">
|
||||||
|
<item-type>Number</item-type>
|
||||||
|
<label>Evapotranspiration</label>
|
||||||
|
<description>Evapotranspiration amount</description>
|
||||||
|
<state readOnly="true" pattern="%.1f"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="precipitation">
|
||||||
|
<item-type>Number</item-type>
|
||||||
|
<label>Precipitation</label>
|
||||||
|
<description>Precipitation amount</description>
|
||||||
|
<state readOnly="true" pattern="%.1f"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="probabilityofprecipitation">
|
||||||
|
<item-type>Number</item-type>
|
||||||
|
<label>Probability Of Precipitation</label>
|
||||||
|
<description>Probability of precipitation percentage</description>
|
||||||
|
<state readOnly="true" pattern="%d%%"/>
|
||||||
|
</channel-type>
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
|||||||
@ -4,14 +4,50 @@
|
|||||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
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">
|
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||||
|
|
||||||
<!-- Sample Thing Type -->
|
<bridge-type id="account">
|
||||||
<thing-type id="cloud">
|
<label>Hydrawise Account Thing</label>
|
||||||
<label>Hydrawise Cloud Thing</label>
|
<description>Hydrawise account</description>
|
||||||
<description>Hydrawise cloud connected irrigation system</description>
|
<config-description>
|
||||||
|
<parameter name="userName" type="text" required="true">
|
||||||
|
<label>User Name</label>
|
||||||
|
<description>Your Hydrawise account user name</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="password" type="text" required="false">
|
||||||
|
<label>Password</label>
|
||||||
|
<context>password</context>
|
||||||
|
<description>Your Hydrawise account password, for security this will not be saved after the first login attempt
|
||||||
|
unless the "Save Password" option is enabled</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="savePassword" type="boolean" required="false">
|
||||||
|
<label>Save Password</label>
|
||||||
|
<description>By default, the password will not be persisted after the first login attempt unless this is enabled</description>
|
||||||
|
<default>false</default>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="refresh" type="integer" required="false" min="30" unit="s">
|
||||||
|
<label>Refresh interval</label>
|
||||||
|
<description>Specifies the refresh interval in seconds</description>
|
||||||
|
<default>60</default>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
</bridge-type>
|
||||||
|
|
||||||
|
<thing-type id="controller">
|
||||||
|
<supported-bridge-type-refs>
|
||||||
|
<bridge-type-ref id="account"/>
|
||||||
|
</supported-bridge-type-refs>
|
||||||
|
<label>Hydrawise Controller Thing</label>
|
||||||
|
<description>Hydrawise connected irrigation controller</description>
|
||||||
|
|
||||||
<!-- Until we have https://github.com/eclipse/smarthome/issues/1118 fixed, we need to list all possible channel groups.
|
<!-- Until we have https://github.com/eclipse/smarthome/issues/1118 fixed, we need to list all possible channel groups.
|
||||||
Once this is fixed we can dynamically add them to the thing and not list them here. -->
|
Once this is fixed we can dynamically add them to the thing and not list them here. -->
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
|
||||||
|
<!-- System -->
|
||||||
|
|
||||||
|
<channel-group id="system" typeId="system"/>
|
||||||
|
|
||||||
|
<!-- Sensors -->
|
||||||
|
|
||||||
<channel-group id="sensor1" typeId="sensor">
|
<channel-group id="sensor1" typeId="sensor">
|
||||||
<label>Sensor 1</label>
|
<label>Sensor 1</label>
|
||||||
<description>Sensor 1</description>
|
<description>Sensor 1</description>
|
||||||
@ -29,6 +65,8 @@
|
|||||||
<description>Sensor 4</description>
|
<description>Sensor 4</description>
|
||||||
</channel-group>
|
</channel-group>
|
||||||
|
|
||||||
|
<!-- Forecasts -->
|
||||||
|
|
||||||
<channel-group id="forecast1" typeId="forecast">
|
<channel-group id="forecast1" typeId="forecast">
|
||||||
<label>Today's Weather</label>
|
<label>Today's Weather</label>
|
||||||
<description>Today's weather forecast</description>
|
<description>Today's weather forecast</description>
|
||||||
@ -41,13 +79,13 @@
|
|||||||
<label>Day 3 Weather</label>
|
<label>Day 3 Weather</label>
|
||||||
<description>Day 3 weather forecast</description>
|
<description>Day 3 weather forecast</description>
|
||||||
</channel-group>
|
</channel-group>
|
||||||
<channel-group id="forecast4" typeId="forecast">
|
|
||||||
<label>Day 4 Weather</label>
|
<!-- All Zones -->
|
||||||
<description>Day 4 weather forecast</description>
|
|
||||||
</channel-group>
|
|
||||||
|
|
||||||
<channel-group id="allzones" typeId="allzones"/>
|
<channel-group id="allzones" typeId="allzones"/>
|
||||||
|
|
||||||
|
<!-- Zones -->
|
||||||
|
|
||||||
<channel-group id="zone1" typeId="zone">
|
<channel-group id="zone1" typeId="zone">
|
||||||
<label>Zone 1</label>
|
<label>Zone 1</label>
|
||||||
<description>Sprinkler Zone 1</description>
|
<description>Sprinkler Zone 1</description>
|
||||||
@ -194,19 +232,9 @@
|
|||||||
</channel-group>
|
</channel-group>
|
||||||
</channel-groups>
|
</channel-groups>
|
||||||
<config-description>
|
<config-description>
|
||||||
<parameter name="apiKey" type="text" required="true">
|
<parameter name="controllerId" type="integer" required="true">
|
||||||
<label>API Key</label>
|
<label>Controller ID</label>
|
||||||
<description>API Key from https://app.hydrawise.com/config/account</description>
|
<description>The ID of a cloud connected irrigation controller
|
||||||
</parameter>
|
|
||||||
<parameter name="refresh" type="integer" required="true">
|
|
||||||
<label>Refresh interval</label>
|
|
||||||
<description>Specifies the refresh interval in seconds</description>
|
|
||||||
<default>30</default>
|
|
||||||
</parameter>
|
|
||||||
<parameter name="controllerId" type="integer" required="false">
|
|
||||||
<label>Optional Controller ID interval</label>
|
|
||||||
<description>Optional parameter to specify the Hydrawise controller ID if you have more then one associated with
|
|
||||||
your account.
|
|
||||||
</description>
|
</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
@ -214,7 +242,7 @@
|
|||||||
|
|
||||||
<thing-type id="local">
|
<thing-type id="local">
|
||||||
<label>Hydrawise Local Thing</label>
|
<label>Hydrawise Local Thing</label>
|
||||||
<description>Hydrawise local connected irrigation system</description>
|
<description>Hydrawise local connected irrigation controller</description>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
<channel-group id="zone1" typeId="zone">
|
<channel-group id="zone1" typeId="zone">
|
||||||
<label>Zone 1</label>
|
<label>Zone 1</label>
|
||||||
@ -383,6 +411,4 @@
|
|||||||
</parameter>
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
|||||||
@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
me {
|
||||||
|
email
|
||||||
|
lastContact
|
||||||
|
controllers {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
status {
|
||||||
|
summary
|
||||||
|
online
|
||||||
|
lastContact {
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
location {
|
||||||
|
coordinates {
|
||||||
|
latitude
|
||||||
|
longitude
|
||||||
|
}
|
||||||
|
forecast(days: 3) {
|
||||||
|
time
|
||||||
|
updateTime
|
||||||
|
conditions
|
||||||
|
averageWindSpeed {
|
||||||
|
value
|
||||||
|
unit
|
||||||
|
}
|
||||||
|
highTemperature {
|
||||||
|
value
|
||||||
|
unit
|
||||||
|
}
|
||||||
|
lowTemperature {
|
||||||
|
value
|
||||||
|
unit
|
||||||
|
}
|
||||||
|
probabilityOfPrecipitation
|
||||||
|
precipitation {
|
||||||
|
value
|
||||||
|
unit
|
||||||
|
}
|
||||||
|
averageHumidity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zones {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
status {
|
||||||
|
suspendedUntil {
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
icon {
|
||||||
|
id
|
||||||
|
fileName
|
||||||
|
customImage {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
number {
|
||||||
|
value
|
||||||
|
label
|
||||||
|
}
|
||||||
|
scheduledRuns {
|
||||||
|
summary
|
||||||
|
currentRun{
|
||||||
|
id
|
||||||
|
startTime {
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
endTime {
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
duration
|
||||||
|
status {
|
||||||
|
value
|
||||||
|
label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextRun {
|
||||||
|
id
|
||||||
|
startTime {
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
endTime {
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sensors {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
input {
|
||||||
|
number
|
||||||
|
label
|
||||||
|
}
|
||||||
|
status {
|
||||||
|
active
|
||||||
|
waterFlow {
|
||||||
|
value
|
||||||
|
unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model {
|
||||||
|
modeType
|
||||||
|
active
|
||||||
|
offLevel
|
||||||
|
offTimer
|
||||||
|
delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user