mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[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
|
||||
|
||||
### 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.
|
||||
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 |
|
||||
|---------------------------------------|
|
||||
| [Zones](#Zone-Channel-Group) |
|
||||
| [All Zones](#All-Zones-Channel-Group) |
|
||||
| [Sensor](#Sensor-Channel-Group) |
|
||||
| [Forecast](#Sensor-Channel-Group) |
|
||||
#### Controller Thing Supported Channel Groups
|
||||
|
||||
| channel group ID |
|
||||
|-----------------------------------------------|
|
||||
| [Controller](#Cloud-Controller-Channel-Group) |
|
||||
| [Zones](#Zone-Channel-Group) |
|
||||
| [All Zones](#All-Zones-Channel-Group) |
|
||||
| [Sensor](#Sensor-Channel-Group) |
|
||||
| [Forecast](#Sensor-Channel-Group) |
|
||||
|
||||
### 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.
|
||||
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
|
||||
|
||||
* 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
|
||||
|
||||
### Cloud Thing
|
||||
### Account Thing
|
||||
|
||||
| Configuration Name | type | required | Comments |
|
||||
|--------------------|---------|----------|------------------------------------------------------------------------------------|
|
||||
| apiKey | String | True | |
|
||||
| refresh | Integer | True | Defaults to a 30 seconds polling rate |
|
||||
| controllerId | Integer | False | Optional id of the controller if you have more then one registered to your account |
|
||||
| Configuration Name | type | required | Comments |
|
||||
|--------------------|---------|----------|--------------------------------------------------------------------------------------------------------------------------|
|
||||
| userName | String | False | The Hydrawise account user name |
|
||||
| password | String | False | The Hydrawise account password |
|
||||
| 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
|
||||
|
||||
![Account](doc/settings.png)
|
||||
| Configuration Name | type | required | Comments |
|
||||
|--------------------|---------|----------|----------------------|
|
||||
| controllerId | Integer | True | ID of the controller |
|
||||
|
||||
Then copy the API key shown here:
|
||||
|
||||
![API Key](doc/apikey.png)
|
||||
|
||||
### Local Thing
|
||||
|
||||
| Configuration Name | type | required | Comments |
|
||||
|--------------------|---------|----------|-----------------------------------------------------------------------------------------------------------------|
|
||||
| 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. |
|
||||
| refresh | Integer | True | Defaults to a 30 seconds polling rate |
|
||||
|
||||
@ -70,6 +85,12 @@ Then copy the API key shown here:
|
||||
|
||||
### Channel Groups
|
||||
|
||||
#### System Channel Group
|
||||
|
||||
| channel group ID | Description |
|
||||
|------------------|---------------------------------|
|
||||
| system | System status of the controller |
|
||||
|
||||
#### Zone Channel Group
|
||||
|
||||
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
|
||||
|
||||
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 |
|
||||
|------------------|-----------------|
|
||||
| forecast1 | Todays Forecast |
|
||||
| forecast2 | Day 2 Forecast |
|
||||
| forecast3 | Day 3 Forecast |
|
||||
| forecast4 | Day 4 Forecast |
|
||||
|
||||
#### All Zones Channel Group
|
||||
|
||||
@ -114,58 +134,81 @@ A single all zone group are supported per Cloud or Local Thing
|
||||
|
||||
### Channels
|
||||
|
||||
| channel ID | type | Groups | description | Read Write |
|
||||
|-----------------|--------------------|----------------|---------------------------------------------|------------|
|
||||
| name | String | zone, sensor | Descriptive name | R |
|
||||
| icon | String | zone | Icon URL | R |
|
||||
| time | Number | zone | Zone start time in seconds | 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 |
|
||||
| nextrun | DateTime | zone | Next date and time this zone will run | R |
|
||||
| timeleft | Number | zone | Amount of seconds left for the running zone | R |
|
||||
| input | Number | sensor | Sensor input type | R |
|
||||
| mode | Number | sensor | Sensor mode | R |
|
||||
| timer | Number | sensor | Sensor timer | R |
|
||||
| offtimer | Number | sensor | Sensor off time | R |
|
||||
| offlevel | Number | sensor | Sensor off level | R |
|
||||
| active | Switch | sensor | Is sensor active / triggered | R |
|
||||
| temperaturehigh | Number:Temperature | forecast | Daily high temperature | R |
|
||||
| temperaturelow | Number:Temperature | forecast | Daily low temperature | R |
|
||||
| conditions | String | forecast | Daily conditions description | R |
|
||||
| day | String | forecast | Day of week of forecast (Mon-Sun) | R |
|
||||
| humidity | Number | forecast | Daily humidity percentage | R |
|
||||
| wind | Number:Speed | forecast | Daily wind speed | R |
|
||||
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 |
|
||||
| type | Number | zone | Zone type | R |
|
||||
| run | Switch | zone, allzones | Run/Start zone | RW |
|
||||
| runcustom | Number:Time | zone, allzones | Run zone for custom length | W |
|
||||
| suspend | Switch | zone, allzones | Suspend zone | RW |
|
||||
| suspenduntil | DateTime | zone, allzones | Suspend zone unitl specified date | RW |
|
||||
| nextrun | DateTime | zone | Next date and time this zone will run | R |
|
||||
| timeleft | Number:Time | zone | Amount of time left for the running zone | R |
|
||||
| input | Number | sensor | Sensor input type | R |
|
||||
| timer | Number | sensor | Sensor timer | R |
|
||||
| offtimer | Number:Time | sensor | Sensor off timer | R |
|
||||
| offlevel | Number | sensor | Sensor off level | R |
|
||||
| active | Switch | sensor | Is sensor active / triggered | R |
|
||||
| temperaturehigh | Number:Temperature | forecast | Daily high temperature | R |
|
||||
| temperaturelow | Number:Temperature | forecast | Daily low temperature | R |
|
||||
| conditions | String | forecast | Daily conditions description | R |
|
||||
| day | DateTime | forecast | Day of week of forecast (Mon-Sun) | 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
|
||||
|
||||
```
|
||||
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)
|
||||
String SprinklerZone1Name "1 Front Office Yard name" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#name"}
|
||||
Switch SprinklerZone1Run "1 Front Office Yard Run" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#run"}
|
||||
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:controller:myaccount:123456: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"}
|
||||
DateTime SprinklerZone1StartTime "1 Front Office Yard Start Time [%s]" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#nextruntime"}
|
||||
Number SprinklerZone1TimeLeft "1 Front Office Yard Time Left" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#timeleft"}
|
||||
String SprinklerZone1Icon "1 Front Office Yard Icon" (SprinklerZone1) {channel="hydrawise:cloud:home:zone1#icon"}
|
||||
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:controller:myaccount:123456:zone1#nextruntime"}
|
||||
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:controller:myaccount:123456:zone1#icon"}
|
||||
|
||||
Group SprinklerZone2 "2 Back Circle Lawn" (SprinklerZones)
|
||||
String SprinklerZone2Name "2 Back Circle Lawn name" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#name"}
|
||||
Switch SprinklerZone2Run "2 Back Circle Lawn Run" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#run"}
|
||||
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:controller:myaccount:123456: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"}
|
||||
DateTime SprinklerZone2StartTime "2 Back Circle Lawn Start Time" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#nextruntime"}
|
||||
Number SprinklerZone2TimeLeft "2 Back Circle Lawn Time Left" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#timeleft"}
|
||||
String SprinklerZone2Icon "2 Back Circle Lawn Icon" (SprinklerZone2) {channel="hydrawise:cloud:home:zone2#icon"}
|
||||
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:controller:myaccount:123456:zone2#nextruntime"}
|
||||
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:controller:myaccount:123456:zone2#icon"}
|
||||
|
||||
Group SprinklerZone3 "3 Left of Drive Lawn" (SprinklerZones)
|
||||
String SprinklerZone3Name "3 Left of Drive Lawn name" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#name"}
|
||||
Switch SprinklerZone3Run "3 Left of Drive Lawn Run" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#run"}
|
||||
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:controller:myaccount:123456: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"}
|
||||
DateTime SprinklerZone3StartTime "3 Left of Drive Lawn Start Time" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#nextruntime"}
|
||||
Number SprinklerZone3TimeLeft "3 Left of Drive Lawn Time Left" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#timeleft"}
|
||||
String SprinklerZone3Icon "3 Left of Drive Lawn Icon" (SprinklerZone3) {channel="hydrawise:cloud:home:zone3#icon"}
|
||||
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:controller:myaccount:123456:zone3#nextruntime"}
|
||||
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:controller:myaccount:123456:zone3#icon"}
|
||||
```
|
||||
|
||||
|
@ -23,44 +23,57 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HydrawiseBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "hydrawise";
|
||||
|
||||
// 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 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_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_ICON = "icon";
|
||||
public static final String CHANNEL_ZONE_LAST_WATER = "lastwater";
|
||||
public static final String CHANNEL_ZONE_TIME = "time";
|
||||
public static final String CHANNEL_ZONE_STARTTIME = "starttime";
|
||||
public static final String CHANNEL_ZONE_DURATION = "duration";
|
||||
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_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_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_INPUT = "input";
|
||||
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_OFFLEVEL = "offlevel";
|
||||
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_LOW = "temperaturelow";
|
||||
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_WIND = "wind";
|
||||
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_NAME = "name";
|
||||
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 java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
@ -40,14 +43,15 @@ import org.osgi.service.component.annotations.Reference;
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.hydrawise", service = ThingHandlerFactory.class)
|
||||
public class HydrawiseHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream.of(THING_TYPE_CLOUD, THING_TYPE_LOCAL)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT,
|
||||
THING_TYPE_CONTROLLER, THING_TYPE_LOCAL);
|
||||
private HttpClient httpClient;
|
||||
private OAuthFactory oAuthFactory;
|
||||
|
||||
@Activate
|
||||
public HydrawiseHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
|
||||
public HydrawiseHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||
final @Reference OAuthFactory oAuthFactory) {
|
||||
this.oAuthFactory = oAuthFactory;
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@ -60,8 +64,12 @@ public class HydrawiseHandlerFactory extends BaseThingHandlerFactory {
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_CLOUD.equals(thingTypeUID)) {
|
||||
return new HydrawiseCloudHandler(thing, httpClient);
|
||||
if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) {
|
||||
return new HydrawiseAccountHandler((Bridge) thing, httpClient, oAuthFactory);
|
||||
}
|
||||
|
||||
if (THING_TYPE_CONTROLLER.equals(thingTypeUID)) {
|
||||
return new HydrawiseControllerHandler(thing);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@NonNullByDefault
|
||||
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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Thrown when command responses return a error message
|
||||
*
|
||||
* @author Dan Cunningham - Initial contribution
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@NonNullByDefault
|
||||
public class HydrawiseCommandException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HydrawiseCommandException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
@ -12,13 +12,16 @@
|
||||
*/
|
||||
package org.openhab.binding.hydrawise.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Thrown for connection issues to the Hydrawise controller
|
||||
*
|
||||
* @author Dan Cunningham - Initial contribution
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@NonNullByDefault
|
||||
public class HydrawiseConnectionException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HydrawiseConnectionException(Exception 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
|
||||
*/
|
||||
package org.openhab.binding.hydrawise.internal.api;
|
||||
package org.openhab.binding.hydrawise.internal.api.local;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.util.BasicAuthentication;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.hydrawise.internal.api.model.LocalScheduleResponse;
|
||||
import org.openhab.binding.hydrawise.internal.api.model.SetZoneResponse;
|
||||
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.local.dto.LocalScheduleResponse;
|
||||
import org.openhab.binding.hydrawise.internal.api.local.dto.SetZoneResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -92,11 +95,12 @@ public class HydrawiseLocalApiClient {
|
||||
* @throws HydrawiseConnectionException
|
||||
* @throws HydrawiseAuthenticationException
|
||||
*/
|
||||
@Nullable
|
||||
public LocalScheduleResponse getLocalSchedule()
|
||||
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
|
||||
String json = doGet(localGetURL);
|
||||
LocalScheduleResponse response = gson.fromJson(json, LocalScheduleResponse.class);
|
||||
return Objects.requireNonNull(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
@ -10,7 +10,9 @@
|
||||
*
|
||||
* 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
|
||||
@ -19,6 +21,7 @@ package org.openhab.binding.hydrawise.internal.api;
|
||||
* @author Dan Cunningham - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class HydrawiseZoneCommandBuilder {
|
||||
|
||||
private final StringBuilder builder;
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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;
|
||||
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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;
|
||||
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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;
|
||||
|
||||
@ -59,7 +59,5 @@ public class Controller {
|
||||
|
||||
public String statusIcon;
|
||||
|
||||
public Boolean online;
|
||||
|
||||
public List<String> tags = null;
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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;
|
||||
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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;
|
||||
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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.List;
|
||||
@ -22,9 +22,9 @@ import java.util.List;
|
||||
*/
|
||||
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;
|
||||
|
@ -10,7 +10,9 @@
|
||||
*
|
||||
* 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.
|
||||
@ -63,7 +65,8 @@ public class PlanArray {
|
||||
|
||||
public String filetypeall;
|
||||
|
||||
public String plan_type;
|
||||
@SerializedName(value = "plan_type")
|
||||
public String planType;
|
||||
|
||||
public String pushNotification;
|
||||
|
@ -10,9 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.hydrawise.internal.api.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
package org.openhab.binding.hydrawise.internal.api.local.dto;
|
||||
|
||||
/**
|
||||
* The {@link Relay} class models the Relay response message
|
||||
@ -23,27 +21,19 @@ public class Relay {
|
||||
|
||||
public Integer relayId;
|
||||
|
||||
public Integer relay;
|
||||
|
||||
public String name;
|
||||
|
||||
public String icon;
|
||||
|
||||
public String lastwater;
|
||||
|
||||
public Integer time;
|
||||
|
||||
public Integer type;
|
||||
|
||||
@SerializedName("run")
|
||||
public String runTime;
|
||||
public Integer relay;
|
||||
|
||||
@SerializedName("run_seconds")
|
||||
public Integer runTimeSeconds;
|
||||
public String name;
|
||||
|
||||
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.
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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;
|
||||
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* 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.List;
|
||||
@ -30,7 +30,7 @@ public class StatusScheduleResponse extends LocalScheduleResponse {
|
||||
|
||||
public Integer nextpoll;
|
||||
|
||||
public List<Sensor> sensors = new LinkedList<>();
|
||||
public List<Sensor> sensors = new LinkedList<Sensor>();
|
||||
|
||||
public String message;
|
||||
|
||||
@ -52,7 +52,7 @@ public class StatusScheduleResponse extends LocalScheduleResponse {
|
||||
|
||||
public String lastContact;
|
||||
|
||||
public List<Forecast> forecast = new LinkedList<>();
|
||||
public List<Forecast> forecast = new LinkedList<Forecast>();
|
||||
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public class HydrawiseCloudConfiguration {
|
||||
|
||||
/**
|
||||
* Customer API key {@link https://app.hydrawise.com/config/account}
|
||||
*/
|
||||
public String apiKey;
|
||||
|
||||
/**
|
||||
* refresh interval in seconds.
|
||||
*/
|
||||
public Integer refresh;
|
||||
|
||||
@NonNullByDefault
|
||||
public class HydrawiseControllerConfiguration {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @author Dan Cunningham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HydrawiseLocalConfiguration {
|
||||
|
||||
/**
|
||||
* Host or IP for local controller
|
||||
*/
|
||||
public String host;
|
||||
public String host = "";
|
||||
/**
|
||||
* User name (admin) for local controller
|
||||
*/
|
||||
public String username;
|
||||
public String username = "";
|
||||
/**
|
||||
* Password for local controller
|
||||
*/
|
||||
public String password;
|
||||
|
||||
public String password = "";
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
package org.openhab.binding.hydrawise.internal;
|
||||
package org.openhab.binding.hydrawise.internal.handler;
|
||||
|
||||
import static org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants.*;
|
||||
|
||||
@ -19,23 +19,27 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
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.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.model.LocalScheduleResponse;
|
||||
import org.openhab.binding.hydrawise.internal.api.model.Relay;
|
||||
import org.openhab.binding.hydrawise.internal.api.model.Running;
|
||||
import org.openhab.binding.hydrawise.internal.api.local.HydrawiseLocalApiClient;
|
||||
import org.openhab.binding.hydrawise.internal.api.local.dto.LocalScheduleResponse;
|
||||
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.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.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
@ -49,23 +53,22 @@ import org.slf4j.Logger;
|
||||
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.
|
||||
*
|
||||
* @author Dan Cunningham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class HydrawiseHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HydrawiseHandler.class);
|
||||
public class HydrawiseLocalHandler extends BaseThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(HydrawiseLocalHandler.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 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,
|
||||
*/
|
||||
private static long MAX_RUN_TIME = 157680000;
|
||||
private static final long MAX_RUN_TIME = 157680000;
|
||||
|
||||
/**
|
||||
* Minimum amount of time we can poll for updates
|
||||
@ -86,8 +89,28 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
||||
* Future to poll for updated
|
||||
*/
|
||||
|
||||
public HydrawiseHandler(Thing thing) {
|
||||
HydrawiseLocalApiClient client;
|
||||
|
||||
public HydrawiseLocalHandler(Thing thing, HttpClient httpClient) {
|
||||
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
|
||||
@ -120,15 +143,14 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
||||
clearPolling();
|
||||
switch (channelId) {
|
||||
case CHANNEL_ZONE_RUN_CUSTOM:
|
||||
if (!(command instanceof DecimalType)) {
|
||||
if (!(command instanceof QuantityType<?>)) {
|
||||
logger.warn("Invalid command type for run custom {}", command.getClass().getName());
|
||||
return;
|
||||
}
|
||||
if (allCommand) {
|
||||
sendRunAllCommand(((DecimalType) command).intValue());
|
||||
client.runAllRelays(((QuantityType<?>) command).intValue());
|
||||
} else {
|
||||
Objects.requireNonNull(relay);
|
||||
sendRunCommand(((DecimalType) command).intValue(), relay);
|
||||
client.runRelay(((QuantityType<?>) command).intValue(), relay.relay);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_ZONE_RUN:
|
||||
@ -138,16 +160,15 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
||||
}
|
||||
if (allCommand) {
|
||||
if (command == OnOffType.ON) {
|
||||
sendRunAllCommand();
|
||||
client.runAllRelays();
|
||||
} else {
|
||||
sendStopAllCommand();
|
||||
client.stopAllRelays();
|
||||
}
|
||||
} else {
|
||||
Objects.requireNonNull(relay);
|
||||
if (command == OnOffType.ON) {
|
||||
sendRunCommand(relay);
|
||||
client.runRelay(relay.relay);
|
||||
} else {
|
||||
sendStopCommand(relay);
|
||||
client.stopRelay(relay.relay);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
status.relays.forEach(r -> {
|
||||
@ -211,12 +192,8 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
||||
logger.trace("Updateing Zone {} {} ", group, r.name);
|
||||
updateGroupState(group, CHANNEL_ZONE_NAME, new StringType(r.name));
|
||||
updateGroupState(group, CHANNEL_ZONE_TYPE, new DecimalType(r.type));
|
||||
updateGroupState(group, CHANNEL_ZONE_TIME,
|
||||
r.runTimeSeconds != null ? new DecimalType(r.runTimeSeconds) : UnDefType.UNDEF);
|
||||
String icon = r.icon;
|
||||
if (icon != null && !icon.isBlank()) {
|
||||
updateGroupState(group, CHANNEL_ZONE_ICON, new StringType(BASE_IMAGE_URL + icon));
|
||||
}
|
||||
updateGroupState(group, CHANNEL_ZONE_STARTTIME,
|
||||
r.runSeconds != null ? new QuantityType<>(r.runSeconds, Units.SECOND) : UnDefType.UNDEF);
|
||||
if (r.time >= MAX_RUN_TIME) {
|
||||
updateGroupState(group, CHANNEL_ZONE_NEXT_RUN_TIME_TIME, UnDefType.UNDEF);
|
||||
} else {
|
||||
@ -228,32 +205,21 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
||||
.filter(z -> Integer.parseInt(z.relayId) == r.relayId.intValue()).findAny();
|
||||
if (running.isPresent()) {
|
||||
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);
|
||||
|
||||
} else {
|
||||
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,
|
||||
!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")
|
||||
@NonNullByDefault
|
||||
protected class NotConfiguredException extends Exception {
|
||||
NotConfiguredException(String message) {
|
||||
super(message);
|
||||
@ -269,11 +235,19 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
||||
stateMap.clear();
|
||||
relayMap.clear();
|
||||
try {
|
||||
configure();
|
||||
initPolling(0);
|
||||
} catch (NotConfiguredException e) {
|
||||
logger.debug("Configuration error {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
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);
|
||||
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) {
|
||||
logger.debug("Could not connect to service");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
@ -310,7 +284,10 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
||||
*/
|
||||
private void pollControllerInternal() {
|
||||
try {
|
||||
pollController();
|
||||
LocalScheduleResponse response = client.getLocalSchedule();
|
||||
if (response != null) {
|
||||
updateZones(response);
|
||||
}
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
@ -324,4 +301,14 @@ public abstract class HydrawiseHandler extends BaseThingHandler {
|
||||
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>
|
||||
<channel id="name" typeId="name"/>
|
||||
<channel id="icon" typeId="icon"/>
|
||||
<channel id="time" typeId="time"/>
|
||||
<channel id="type" typeId="type"/>
|
||||
<channel id="runcustom" typeId="runcustom"/>
|
||||
<channel id="run" typeId="run"/>
|
||||
<channel id="runcustom" typeId="runcustom"/>
|
||||
<channel id="nextruntime" typeId="nextruntime"/>
|
||||
<channel id="suspend" typeId="suspend"/>
|
||||
<channel id="suspenduntil" typeId="suspenduntil"/>
|
||||
<channel id="timeleft" typeId="timeleft"/>
|
||||
<channel id="summary" typeId="summary"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
@ -25,6 +27,8 @@
|
||||
<channels>
|
||||
<channel id="runcustom" typeId="runcustom"/>
|
||||
<channel id="run" typeId="run"/>
|
||||
<channel id="suspend" typeId="suspend"/>
|
||||
<channel id="suspenduntil" typeId="suspenduntil"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
@ -34,11 +38,11 @@
|
||||
<channels>
|
||||
<channel id="name" typeId="name"/>
|
||||
<channel id="input" typeId="input"/>
|
||||
<channel id="mode" typeId="mode"/>
|
||||
<channel id="timer" typeId="timer"/>
|
||||
<channel id="delay" typeId="delay"/>
|
||||
<channel id="offtimer" typeId="offtimer"/>
|
||||
<channel id="offlevel" typeId="offlevel"/>
|
||||
<channel id="active" typeId="active"/>
|
||||
<channel id="waterflow" typeId="waterflow"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
@ -49,13 +53,43 @@
|
||||
<channel id="temperaturehigh" typeId="temperaturehigh"/>
|
||||
<channel id="temperaturelow" typeId="temperaturelow"/>
|
||||
<channel id="conditions" typeId="conditions"/>
|
||||
<channel id="day" typeId="day"/>
|
||||
<channel id="time" typeId="time"/>
|
||||
<channel id="humidity" typeId="humidity"/>
|
||||
<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>
|
||||
</channel-group-type>
|
||||
|
||||
<!-- 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">
|
||||
<item-type>String</item-type>
|
||||
<label>Name</label>
|
||||
@ -70,10 +104,17 @@
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="time" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<channel-type id="starttime" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<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>
|
||||
</channel-type>
|
||||
|
||||
@ -87,7 +128,7 @@
|
||||
<channel-type id="nextruntime">
|
||||
<item-type>DateTime</item-type>
|
||||
<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>
|
||||
</channel-type>
|
||||
|
||||
@ -98,13 +139,25 @@
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="runcustom">
|
||||
<item-type>Number</item-type>
|
||||
<label>Run Zones With Custom Duration </label>
|
||||
<description>Run zones now for a custom duration of time in seconds</description>
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Run Zones With Custom Duration</label>
|
||||
<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 id="timeleft">
|
||||
<item-type>Number</item-type>
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Time Left Seconds</label>
|
||||
<description>Time left that zone will run for</description>
|
||||
<state readOnly="true"></state>
|
||||
@ -126,22 +179,15 @@
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="mode" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Mode</label>
|
||||
<description>Sensor mode</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>
|
||||
<channel-type id="delay" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Delay</label>
|
||||
<description>Sensor delay</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="offtimer" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Off Timer</label>
|
||||
<description>Sensor off timer</description>
|
||||
<state readOnly="true"></state>
|
||||
@ -160,6 +206,7 @@
|
||||
<description>Sensor off level</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="active">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Active</label>
|
||||
@ -167,6 +214,13 @@
|
||||
<state readOnly="true"></state>
|
||||
</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 -->
|
||||
<channel-type id="temperaturehigh">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
@ -198,10 +252,10 @@
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="day">
|
||||
<item-type>String</item-type>
|
||||
<label>Day of Week</label>
|
||||
<description>Day of week for the weather forecast</description>
|
||||
<channel-type id="time">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Forecast Time</label>
|
||||
<description>Forecast date and time</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
@ -220,4 +274,25 @@
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</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>
|
||||
|
@ -4,14 +4,50 @@
|
||||
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">
|
||||
|
||||
<!-- Sample Thing Type -->
|
||||
<thing-type id="cloud">
|
||||
<label>Hydrawise Cloud Thing</label>
|
||||
<description>Hydrawise cloud connected irrigation system</description>
|
||||
<bridge-type id="account">
|
||||
<label>Hydrawise Account Thing</label>
|
||||
<description>Hydrawise account</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.
|
||||
Once this is fixed we can dynamically add them to the thing and not list them here. -->
|
||||
<channel-groups>
|
||||
|
||||
<!-- System -->
|
||||
|
||||
<channel-group id="system" typeId="system"/>
|
||||
|
||||
<!-- Sensors -->
|
||||
|
||||
<channel-group id="sensor1" typeId="sensor">
|
||||
<label>Sensor 1</label>
|
||||
<description>Sensor 1</description>
|
||||
@ -29,6 +65,8 @@
|
||||
<description>Sensor 4</description>
|
||||
</channel-group>
|
||||
|
||||
<!-- Forecasts -->
|
||||
|
||||
<channel-group id="forecast1" typeId="forecast">
|
||||
<label>Today's Weather</label>
|
||||
<description>Today's weather forecast</description>
|
||||
@ -41,13 +79,13 @@
|
||||
<label>Day 3 Weather</label>
|
||||
<description>Day 3 weather forecast</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecast4" typeId="forecast">
|
||||
<label>Day 4 Weather</label>
|
||||
<description>Day 4 weather forecast</description>
|
||||
</channel-group>
|
||||
|
||||
<!-- All Zones -->
|
||||
|
||||
<channel-group id="allzones" typeId="allzones"/>
|
||||
|
||||
<!-- Zones -->
|
||||
|
||||
<channel-group id="zone1" typeId="zone">
|
||||
<label>Zone 1</label>
|
||||
<description>Sprinkler Zone 1</description>
|
||||
@ -194,19 +232,9 @@
|
||||
</channel-group>
|
||||
</channel-groups>
|
||||
<config-description>
|
||||
<parameter name="apiKey" type="text" required="true">
|
||||
<label>API Key</label>
|
||||
<description>API Key from https://app.hydrawise.com/config/account</description>
|
||||
</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.
|
||||
<parameter name="controllerId" type="integer" required="true">
|
||||
<label>Controller ID</label>
|
||||
<description>The ID of a cloud connected irrigation controller
|
||||
</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
@ -214,7 +242,7 @@
|
||||
|
||||
<thing-type id="local">
|
||||
<label>Hydrawise Local Thing</label>
|
||||
<description>Hydrawise local connected irrigation system</description>
|
||||
<description>Hydrawise local connected irrigation controller</description>
|
||||
<channel-groups>
|
||||
<channel-group id="zone1" typeId="zone">
|
||||
<label>Zone 1</label>
|
||||
@ -383,6 +411,4 @@
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
|
||||
</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…
Reference in New Issue
Block a user