added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.hydrawise</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,171 @@
# Hydrawise Binding
![API Key](doc/logo.png)
The Hydrawise binding allows monitoring and control of [Hunter Industries's](https://www.hunterindustries.com/) line of [Hydrawise](https://www.hydrawise.com) internet connected irrigation systems.
## Supported Things
### Cloud Thing
The Cloud 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
| channel group ID |
|---------------------------------------|
| [Zones](#Zone-Channel-Group) |
| [All Zones](#All-Zones-Channel-Group) |
| [Sensor](#Sensor-Channel-Group) |
| [Forecast](#Sensor-Channel-Group) |
### Local Thing
The Local Thing type uses an undocumented API that allows direct HTTP access to a irrigation controller on the user's network.
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.
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.
* This is also useful if you wish to not use the cloud scheduling at all and use openHAB as the irrigation scheduling system.
#### Local Thing Supported Channel Groups
| channel group ID |
|---------------------------------------|
| [Zones](#Zone-Channel-Group) |
| [All Zones](#All-Zones-Channel-Group) |
## Thing Configuration
### Cloud 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 |
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:
![Account](doc/settings.png)
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 |
| 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 |
## Channels
### Channel Groups
#### Zone Channel Group
Up to 36 total zones are supported per Local or Cloud thing
| channel group ID | Description |
|------------------|---------------------------|
| zone1 | Zone 1 channel group |
| zone2 | Zone 1 channel group |
| ... | Zone 3 - 35 channel group |
| zone36 | Zone 36 channel group |
#### Sensor Channel Group
Up to 4 total sensors are supported per Cloud Thing
| channel group ID | Description |
|------------------|------------------------|
| sensor1 | Sensor 1 channel group |
| sensor2 | Sensor 2 channel group |
| sensor3 | Sensor 3 channel group |
| sensor4 | Sensor 4 channel group |
#### Forecast Channel Group
Up to 4 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
A single all zone group are supported per Cloud or Local Thing
| channel group ID | Description |
|------------------|------------------------|
| allzones | commands for all zones |
### 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 |
## Full Example
```
Group SprinklerZones
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"}
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"}
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"}
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"}
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"}
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"}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.hydrawise</artifactId>
<name>openHAB Add-ons :: Bundles :: Hydrawise Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.hydrawise-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-hydrawise" description="Hydrawise Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.hydrawise/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2020 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.openhab.core.thing.ThingTypeUID;
/**
* The {@link HydrawiseBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Dan Cunningham - Initial contribution
*/
@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_LOCAL = new ThingTypeUID(BINDING_ID, "local");
public static final String BASE_IMAGE_URL = "https://app.hydrawise.com/config/images/";
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_TYPE = "type";
public static final String CHANNEL_ZONE_NEXT_RUN_TIME_TIME = "nextruntime";
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_OFFTIMER = "offtimer";
public static final String CHANNEL_SENSOR_OFFLEVEL = "offlevel";
public static final String CHANNEL_SENSOR_ACTIVE = "active";
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_HUMIDITY = "humidity";
public static final String CHANNEL_FORECAST_WIND = "wind";
public static final String CHANNEL_FORECAST_ICON = "icon";
public static final String PROPERTY_CONTROLLER_ID = "controller";
public static final String PROPERTY_NAME = "name";
public static final String PROPERTY_DESCRIPTION = "description";
public static final String PROPERTY_LOCATION = "location";
public static final String PROPERTY_ADDRESS = "address";
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link HydrawiseCloudConfiguration} 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;
/**
* optional id of the controller to connect to
*/
public Integer controllerId;
}

View File

@@ -0,0 +1,245 @@
/**
* Copyright (c) 2010-2020 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.apache.commons.lang.StringUtils;
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 (StringUtils.isNotBlank(controllerId)) {
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;
}
}

View File

@@ -0,0 +1,324 @@
/**
* Copyright (c) 2010-2020 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.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
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 HydrawiseHandler} 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);
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;
/**
* Minimum amount of time we can poll for updates
*/
protected static final int MIN_REFRESH_SECONDS = 5;
/**
* Minimum amount of time we can poll after a command
*/
protected static final int COMMAND_REFRESH_SECONDS = 5;
/**
* Our poll rate
*/
protected int refresh;
/**
* Future to poll for updated
*/
public HydrawiseHandler(Thing thing) {
super(thing);
}
@SuppressWarnings({ "null", "unused" }) // compiler does not like relayMap.get can return null
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (getThing().getStatus() != ThingStatus.ONLINE) {
logger.warn("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;
}
String group = channelUID.getGroupId();
String channelId = channelUID.getIdWithoutGroup();
boolean allCommand = CHANNEL_GROUP_ALLZONES.equals(group);
Relay relay = relayMap.get(group);
if (!allCommand && relay == null) {
logger.debug("Zone not found {}", group);
return;
}
try {
clearPolling();
switch (channelId) {
case CHANNEL_ZONE_RUN_CUSTOM:
if (!(command instanceof DecimalType)) {
logger.warn("Invalid command type for run custom {}", command.getClass().getName());
return;
}
if (allCommand) {
sendRunAllCommand(((DecimalType) command).intValue());
} else {
sendRunCommand(((DecimalType) command).intValue(), relay);
}
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) {
sendRunAllCommand();
} else {
sendStopAllCommand();
}
} else {
if (command == OnOffType.ON) {
sendRunCommand(relay);
} else {
sendStopCommand(relay);
}
}
break;
}
initPolling(COMMAND_REFRESH_SECONDS);
} catch (HydrawiseCommandException | HydrawiseConnectionException e) {
logger.debug("Could not issue command", e);
initPolling(COMMAND_REFRESH_SECONDS);
} catch (HydrawiseAuthenticationException e) {
logger.debug("Credentials not valid");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Credentials not valid");
configureInternal();
}
}
@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 -> {
String group = "zone" + r.getRelayNumber();
relayMap.put(group, r);
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);
if (StringUtils.isNotBlank(r.icon)) {
updateGroupState(group, CHANNEL_ZONE_ICON, new StringType(BASE_IMAGE_URL + r.icon));
}
if (r.time >= MAX_RUN_TIME) {
updateGroupState(group, CHANNEL_ZONE_NEXT_RUN_TIME_TIME, UnDefType.UNDEF);
} else {
updateGroupState(group, CHANNEL_ZONE_NEXT_RUN_TIME_TIME,
new DateTimeType(now.plusSeconds(r.time).truncatedTo(ChronoUnit.MINUTES)));
}
Optional<Running> running = status.running.stream()
.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));
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(CHANNEL_GROUP_ALLZONES, CHANNEL_ZONE_RUN,
!status.running.isEmpty() ? 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);
}
}
private boolean isFutureValid(@Nullable ScheduledFuture<?> future) {
return future != null && !future.isCancelled();
}
private void configureInternal() {
clearPolling();
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());
} catch (HydrawiseConnectionException e) {
logger.debug("Could not connect to service");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (HydrawiseAuthenticationException e) {
logger.debug("Credentials not valid");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Credentials not valid");
}
}
/**
* 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) {
clearPolling();
pollFuture = scheduler.scheduleWithFixedDelay(this::pollControllerInternal, 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);
}
}
}
/**
* Poll the controller for updates.
*/
private void pollControllerInternal() {
try {
pollController();
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
} catch (HydrawiseConnectionException e) {
// poller will continue to run, set offline until next run
logger.debug("Exception polling", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (HydrawiseAuthenticationException e) {
// if are creds are not valid, we need to try re authorizing again
logger.debug("Authorization exception during polling", e);
configureInternal();
}
}
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2020 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.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.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link HydrawiseHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Dan Cunningham - Initial contribution
*/
@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;
@Activate
public HydrawiseHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
this.httpClient = httpClientFactory.getCommonHttpClient();
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_CLOUD.equals(thingTypeUID)) {
return new HydrawiseCloudHandler(thing, httpClient);
}
if (THING_TYPE_LOCAL.equals(thingTypeUID)) {
return new HydrawiseLocalHandler(thing, httpClient);
}
return null;
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link HydrawiseLocalConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Dan Cunningham - Initial contribution
*/
public class HydrawiseLocalConfiguration {
/**
* Host or IP for local controller
*/
public String host;
/**
* User name (admin) for local controller
*/
public String username;
/**
* Password for local controller
*/
public String password;
/**
* refresh interval in seconds.
*/
public int refresh;
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2010-2020 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();
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 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;
/**
* Thrown when the Hydrawise cloud or local API returns back a "unauthorized" response to commands
*
* @author Dan Cunningham - Initial contribution
*/
@SuppressWarnings("serial")
public class HydrawiseAuthenticationException extends Exception {
}

View File

@@ -0,0 +1,311 @@
/**
* Copyright (c) 2010-2020 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.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 = 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 = gson.fromJson(json, CustomerDetailsResponse.class);
throwExceptionIfResponseError(response);
return response;
}
/***
* Sets the controller with supplied {@value 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 = 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 = 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);
}
}
}
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 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;
/**
* Thrown when command responses return a error message
*
* @author Dan Cunningham - Initial contribution
*/
@SuppressWarnings("serial")
public class HydrawiseCommandException extends Exception {
public HydrawiseCommandException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 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;
/**
* Thrown for connection issues to the Hydrawise controller
*
* @author Dan Cunningham - Initial contribution
*/
@SuppressWarnings("serial")
public class HydrawiseConnectionException extends Exception {
public HydrawiseConnectionException(Exception e) {
super(e);
}
public HydrawiseConnectionException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,214 @@
/**
* Copyright (c) 2010-2020 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.net.URI;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* The {@link HydrawiseLocalApiClient} communicates with a network local Hydrawise controller.
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
public class HydrawiseLocalApiClient {
private final Logger logger = LoggerFactory.getLogger(HydrawiseLocalApiClient.class);
private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
private static final String GET_LOCAL_DATA_URL = "%s/get_sched_json.php?hours=720";
private static final String SET_LOCAL_DATA_URL = "%s/set_manual_data.php?period_id=998";
private static final int TIMEOUT_SECONDS = 30;
private HttpClient httpClient;
private String localSetURL = "";
private String localGetURL = "";
public HydrawiseLocalApiClient(HttpClient httpClient) {
this.httpClient = httpClient;
}
/**
* Initializes the {@link HydrawiseLocalApiClient} to talk with the network local Hydrawise API
*
* @param host
* @param username
* @param password
*/
public HydrawiseLocalApiClient(String host, String username, String password, HttpClient httpClient) {
this.httpClient = httpClient;
setCredentials(host, username, password);
}
/**
* Sets the local credentials and controller host
*
* @param host
* @param username
* @param password
*/
public void setCredentials(String host, String username, String password) {
String url = "http://" + host;
localSetURL = String.format(SET_LOCAL_DATA_URL, url);
localGetURL = String.format(GET_LOCAL_DATA_URL, url);
AuthenticationStore auth = httpClient.getAuthenticationStore();
URI uri = URI.create(url);
auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, username, password));
}
/**
* Retrieves the {@link LocalScheduleResponse} for the controller
*
* @return the local schedule response
* @throws HydrawiseConnectionException
* @throws HydrawiseAuthenticationException
*/
public LocalScheduleResponse getLocalSchedule()
throws HydrawiseConnectionException, HydrawiseAuthenticationException {
String json = doGet(localGetURL);
LocalScheduleResponse response = gson.fromJson(json, LocalScheduleResponse.class);
return response;
}
/**
* Stops a given relay
*
* @param number
* @return Response message
* @throws HydrawiseConnectionException
* @throws HydrawiseAuthenticationException
* @throws HydrawiseCommandException
*/
public String stopRelay(int number)
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
return relayCommand(new HydrawiseZoneCommandBuilder(localSetURL).action("stop").relayNumber(number).toString());
}
/**
* Runs a given relay for the default amount of time
*
* @param number
* @return Response message
* @throws HydrawiseConnectionException
* @throws HydrawiseAuthenticationException
* @throws HydrawiseCommandException
*/
public String runRelay(int number)
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
return relayCommand(new HydrawiseZoneCommandBuilder(localSetURL).action("run").relayNumber(number).toString());
}
/**
* Runs a given relay for a specified numbers of seconds
*
* @param seconds
* @param number
* @return Response message
* @throws HydrawiseConnectionException
* @throws HydrawiseAuthenticationException
* @throws HydrawiseCommandException
*/
public String runRelay(int seconds, int number)
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
return relayCommand(new HydrawiseZoneCommandBuilder(localSetURL).action("run").relayNumber(number)
.duration(seconds).toString());
}
/**
* Stops all relays
*
* @return Response message
* @throws HydrawiseConnectionException
* @throws HydrawiseAuthenticationException
* @throws HydrawiseCommandException
*/
public String stopAllRelays()
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
return relayCommand(new HydrawiseZoneCommandBuilder(localSetURL).action("stopall").toString());
}
/**
* Run all relays for the default amount of time
*
* @return Response message
* @throws HydrawiseConnectionException
* @throws HydrawiseAuthenticationException
* @throws HydrawiseCommandException
*/
public String runAllRelays()
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
return relayCommand(new HydrawiseZoneCommandBuilder(localSetURL).action("runall").toString());
}
/**
* Run all relays for a given amount of seconds
*
* @param seconds
* @return Response message
* @throws HydrawiseConnectionException
* @throws HydrawiseAuthenticationException
* @throws HydrawiseCommandException
*/
public String runAllRelays(int seconds)
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
return relayCommand(new HydrawiseZoneCommandBuilder(localSetURL).action("runall").duration(seconds).toString());
}
private String relayCommand(String url)
throws HydrawiseConnectionException, HydrawiseAuthenticationException, HydrawiseCommandException {
String json = doGet(url);
SetZoneResponse response = gson.fromJson(json, SetZoneResponse.class);
if (response.messageType.equals("error")) {
throw new HydrawiseCommandException(response.message);
}
return response.message;
}
private String doGet(String url) throws HydrawiseConnectionException, HydrawiseAuthenticationException {
logger.trace("Getting {}", url);
ContentResponse response;
try {
response = httpClient.newRequest(url).method(HttpMethod.GET).timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.send();
} catch (InterruptedException | TimeoutException | ExecutionException e) {
throw new HydrawiseConnectionException(e);
}
if (response.getStatus() == 401) {
throw new HydrawiseAuthenticationException();
}
if (response.getStatus() != 200) {
throw new HydrawiseConnectionException("Error from controller. Response code " + response.getStatus());
}
String stringResponse = response.getContentAsString();
logger.trace("Response: {}", stringResponse);
return stringResponse;
}
}

View File

@@ -0,0 +1,105 @@
/**
* Copyright (c) 2010-2020 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;
/**
* The {@link HydrawiseZoneCommandBuilder} class builds a command URL string to use when sending commands to the
* Hydrawise local controller or cloud based API server
*
* @author Dan Cunningham - Initial contribution
*
*/
class HydrawiseZoneCommandBuilder {
private final StringBuilder builder;
/**
* Construct a new {@link HydrawiseZoneCommandBuilder} class with a base URL
*
* @param baseURL
*/
public HydrawiseZoneCommandBuilder(String baseURL) {
builder = new StringBuilder(baseURL);
}
/**
* Construct a new {@link HydrawiseZoneCommandBuilder} class with a base URL and API key.
*
* @param baseURL
* @param apiKey
*/
public HydrawiseZoneCommandBuilder(String baseURL, String apiKey) {
this(baseURL);
builder.append("&api_key=" + apiKey);
}
/**
* Sets the action parameter
*
* @param action
* @return {@link HydrawiseZoneCommandBuilder}
*/
public HydrawiseZoneCommandBuilder action(String action) {
builder.append("&action=" + action);
return this;
}
/**
* Sets the relayId parameter
*
* @param action
* @return {@link HydrawiseZoneCommandBuilder}
*/
public HydrawiseZoneCommandBuilder relayId(int relayId) {
builder.append("&relay_id=" + relayId);
return this;
}
/**
* Sets the relay number parameter
*
* @param action
* @return {@link HydrawiseZoneCommandBuilder}
*/
public HydrawiseZoneCommandBuilder relayNumber(int number) {
builder.append("&relay=" + number);
return this;
}
/**
* Sets the run duration parameter
*
* @param action
* @return {@link HydrawiseZoneCommandBuilder}
*/
public HydrawiseZoneCommandBuilder duration(int seconds) {
builder.append("&custom=" + seconds);
return this;
}
/**
* Sets the controller Id parameter
*
* @param action
* @return {@link HydrawiseZoneCommandBuilder}
*/
public HydrawiseZoneCommandBuilder controllerId(int controllerId) {
builder.append("&controller_id=" + controllerId);
return this;
}
@Override
public String toString() {
return builder.toString();
}
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.List;
/**
* The {@link BocTopologyDesired} class models the actual BocTopology
*
* @author Dan Cunningham - Initial contribution
*/
public class BocTopologyActual {
public List<Object> bocGateways;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.List;
/**
* The {@link BocTopologyDesired} class models the desired BocTopology
*
* @author Dan Cunningham - Initial contribution
*/
public class BocTopologyDesired {
public List<Object> bocGateways;
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.List;
/**
* The {@link Controller} class models a Hydrawise controller unit
*
* @author Dan Cunningham - Initial contribution
*/
public class Controller {
public String name;
public Integer lastContact;
public String serialNumber;
public Integer controllerId;
public String swVersion;
public String hardware;
public Boolean isBoc;
public String address;
public String timezone;
public Integer deviceId;
public Object parentDeviceId;
public String image;
public String description;
public Integer customerId;
public Double latitude;
public Double longitude;
public String lastContactReadable;
public String status;
public String statusIcon;
public Boolean online;
public List<String> tags = null;
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.List;
/**
* The {@link CustomerDetailsResponse} class models the CustomerDetails response message
*
* @author Dan Cunningham - Initial contribution
*/
public class CustomerDetailsResponse extends Response {
public BocTopologyDesired bocTopologyDesired;
public BocTopologyActual bocTopologyActual;
public List<Controller> controllers;
public String currentController;
public Boolean isBoc;
public Integer tandc;
public Integer controllerId;
public Integer customerId;
public String sessionId;
public String hardwareVersion;
public Integer deviceId;
public Integer tandcVersion;
public Features features;
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.List;
/**
* The {@link Features} class models an accounts features.
*
* @author Dan Cunningham - Initial contribution
*/
public class Features {
public List<PlanArray> planArray = null;
public Object id;
public String planType2;
public String planType2Key;
public Object sku;
public String discount;
public String cost;
public String costUs;
public String costAu;
public String costEu;
public String costCa;
public String costUk;
public String active;
public String controllerQty;
public String rainfall;
public String smsQty;
public String scheduledReports;
public String emailAlerts;
public String defineSensor;
public String addUser;
public String contractor;
public Object description;
public String sensorPack;
public String filelimit;
public String filetypeall;
public String planType;
public String pushNotification;
public String weatherQty;
public String weatherFreeQty;
public String reportingDays;
public String weatherHourlyUpdates;
public String freeEnthusiastPlans;
public String visible;
public Object contractorPurchasable;
public Integer boc;
public Object expiry;
public Object start;
public String customerplanId;
public Integer smsUsed;
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link Forecast} class models a daily weather forecast
*
* @author Dan Cunningham - Initial contribution
*/
public class Forecast {
public String tempHi;
public String tempLo;
public String conditions;
public String day;
public Integer pop;
public Integer humidity;
public String wind;
public String icon;
public String iconLocal;
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.LinkedList;
import java.util.List;
/**
* The {@link LocalScheduleResponse} class models the LocalSchedule response message
*
* @author Dan Cunningham - Initial contribution
*/
public class LocalScheduleResponse extends Response {
public List<Running> running = new LinkedList<>();
public List<Relay> relays = new LinkedList<>();
public String name;
public Integer time;
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link PlanArray} class models am account plan.
*
* @author Dan Cunningham - Initial contribution
*/
public class PlanArray {
public String id;
public Object sku;
public String discount;
public String cost;
public String costUs;
public String costAu;
public String costEu;
public String costCa;
public String costUk;
public String active;
public String controllerQty;
public String rainfall;
public String smsQty;
public String scheduledReports;
public String emailAlerts;
public String defineSensor;
public String addUser;
public String contractor;
public String description;
public String sensorPack;
public String filelimit;
public String filetypeall;
public String plan_type;
public String pushNotification;
public String weatherQty;
public String weatherFreeQty;
public String reportingDays;
public String weatherHourlyUpdates;
public String freeEnthusiastPlans;
public String visible;
public String contractorPurchasable;
public String boc;
public String expiry;
public String start;
public String customerplanId;
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 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.model;
import com.google.gson.annotations.SerializedName;
/**
* The {@link Relay} class models the Relay response message
*
* @author Dan Cunningham - Initial contribution
*/
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;
@SerializedName("run_seconds")
public Integer runTimeSeconds;
public String nicetime;
public String id;
/**
* Returns back the actual relay number when multiple controllers are chained.
*
* @return
*/
public int getRelayNumber() {
int quotient = relay / 100;
return (relay - (quotient * 100)) + (quotient * 12);
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link Response} class models Response messages
*
* @author Dan Cunningham - Initial contribution
*/
public class Response {
public String errorMsg;
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link Running} class models a running relay
*
* @author Dan Cunningham - Initial contribution
*/
public class Running {
public String relay;
public String relayId;
public Integer timeLeft;
public String run;
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.List;
/**
* The {@link Sensor} class models a sensor
*
* @author Dan Cunningham - Initial contribution
*/
public class Sensor {
public Integer input;
public Integer type;
public Integer mode;
public Integer timer;
public Integer offtimer;
public String name;
public Integer offlevel;
public Integer active;
public List<Object> relays = null;
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link SetControllerResponse} class models the SetController response message
*
* @author Dan Cunningham - Initial contribution
*/
public class SetControllerResponse extends Response {
public String name;
public String controllerId;
public String message;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 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.model;
/**
* The {@link SetZoneResponse} class models the SetZone response message
*
* @author Dan Cunningham - Initial contribution
*/
public class SetZoneResponse extends Response {
public String message;
public String messageType;
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 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.model;
import java.util.LinkedList;
import java.util.List;
/**
* The {@link StatusScheduleResponse} class models the Status and Schedule response message
*
* @author Dan Cunningham - Initial contribution
*/
public class StatusScheduleResponse extends LocalScheduleResponse {
public Integer controllerId;
public Integer customerId;
public Integer userId;
public Integer nextpoll;
public List<Sensor> sensors = new LinkedList<>();
public String message;
public String obsRain;
public String obsRainWeek;
public String obsMaxtemp;
public Integer obsRainUpgrade;
public String obsRainText;
public String obsCurrenttemp;
public String wateringTime;
public Integer waterSaving;
public String lastContact;
public List<Forecast> forecast = new LinkedList<>();
public String status;
public String statusIcon;
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="hydrawise" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Hydrawise Binding</name>
<description>This is the binding for Hydrawise irrigation systems.</description>
<author>Dan Cunningham</author>
</binding:binding>

View File

@@ -0,0 +1,223 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="hydrawise"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-group-type id="zone">
<label>Zone</label>
<description>Hydrawise zone</description>
<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="nextruntime" typeId="nextruntime"/>
<channel id="timeleft" typeId="timeleft"/>
</channels>
</channel-group-type>
<channel-group-type id="allzones">
<label>All Zones</label>
<description>Commands that control all Hydrawise zones</description>
<channels>
<channel id="runcustom" typeId="runcustom"/>
<channel id="run" typeId="run"/>
</channels>
</channel-group-type>
<channel-group-type id="sensor">
<label>Sensor</label>
<description>Hydrawise sensor</description>
<channels>
<channel id="name" typeId="name"/>
<channel id="input" typeId="input"/>
<channel id="mode" typeId="mode"/>
<channel id="timer" typeId="timer"/>
<channel id="offtimer" typeId="offtimer"/>
<channel id="offlevel" typeId="offlevel"/>
<channel id="active" typeId="active"/>
</channels>
</channel-group-type>
<channel-group-type id="forecast">
<label>Weather Forecast</label>
<description>Hydrawise weather forecast</description>
<channels>
<channel id="temperaturehigh" typeId="temperaturehigh"/>
<channel id="temperaturelow" typeId="temperaturelow"/>
<channel id="conditions" typeId="conditions"/>
<channel id="day" typeId="day"/>
<channel id="humidity" typeId="humidity"/>
<channel id="wind" typeId="wind"/>
</channels>
</channel-group-type>
<!-- Controller -->
<channel-type id="name">
<item-type>String</item-type>
<label>Name</label>
<description>Name</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="icon">
<item-type>String</item-type>
<label>Icon URL</label>
<description>Icon URL for this zone</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="time" advanced="true">
<item-type>Number</item-type>
<label>Start Time</label>
<description>Zone start time in seconds</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="type" advanced="true">
<item-type>Number</item-type>
<label>Type</label>
<description>Zone Type</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="nextruntime">
<item-type>DateTime</item-type>
<label>Next Run Time</label>
<description>Next time this zone is scheduled to run</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="run">
<item-type>Switch</item-type>
<label>Run Zones</label>
<description>Run or stop zones for the default time.</description>
</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>
</channel-type>
<channel-type id="timeleft">
<item-type>Number</item-type>
<label>Time Left Seconds</label>
<description>Time left that zone will run for</description>
<state readOnly="true"></state>
</channel-type>
<!-- Sensor -->
<channel-type id="input" advanced="true">
<item-type>Number</item-type>
<label>Input</label>
<description>Sensor input</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="type" advanced="true">
<item-type>Number</item-type>
<label>Type</label>
<description>Sensor type</description>
<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>
<state readOnly="true"></state>
</channel-type>
<channel-type id="offtimer" advanced="true">
<item-type>Number</item-type>
<label>Off Timer</label>
<description>Sensor off timer</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="name" advanced="true">
<item-type>String</item-type>
<label>Name</label>
<description>Sensor name</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="offlevel" advanced="true">
<item-type>Number</item-type>
<label>Off Level</label>
<description>Sensor off level</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="active">
<item-type>Switch</item-type>
<label>Active</label>
<description>Sensor active</description>
<state readOnly="true"></state>
</channel-type>
<!-- Weather Forecast -->
<channel-type id="temperaturehigh">
<item-type>Number:Temperature</item-type>
<label>High temperature</label>
<description>High temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="temperaturelow">
<item-type>Number:Temperature</item-type>
<label>Low Temperature</label>
<description>Low Temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="conditions">
<item-type>String</item-type>
<label>Conditions</label>
<description>Weather conditions</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="conditions">
<item-type>String</item-type>
<label>Conditions</label>
<description>Weather conditions</description>
<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>
<state readOnly="true"></state>
</channel-type>
<channel-type id="humidity">
<item-type>Number</item-type>
<label>Humidity</label>
<description>Humidity percentage</description>
<category>Temperature</category>
<state readOnly="true" pattern="%d%%"/>
</channel-type>
<channel-type id="wind">
<item-type>Number:Speed</item-type>
<label>Wind Speed</label>
<description>Wind speed</description>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,388 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="hydrawise"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Sample Thing Type -->
<thing-type id="cloud">
<label>Hydrawise Cloud Thing</label>
<description>Hydrawise cloud connected irrigation system</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>
<channel-group id="sensor1" typeId="sensor">
<label>Sensor 1</label>
<description>Sensor 1</description>
</channel-group>
<channel-group id="sensor2" typeId="sensor">
<label>Sensor 2</label>
<description>Sensor 2</description>
</channel-group>
<channel-group id="sensor3" typeId="sensor">
<label>Sensor 3</label>
<description>Sensor 3</description>
</channel-group>
<channel-group id="sensor4" typeId="sensor">
<label>Sensor 4</label>
<description>Sensor 4</description>
</channel-group>
<channel-group id="forecast1" typeId="forecast">
<label>Today's Weather</label>
<description>Today's weather forecast</description>
</channel-group>
<channel-group id="forecast2" typeId="forecast">
<label>Day 2 Weather</label>
<description>Day 2 weather forecast</description>
</channel-group>
<channel-group id="forecast3" typeId="forecast">
<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>
<channel-group id="allzones" typeId="allzones"/>
<channel-group id="zone1" typeId="zone">
<label>Zone 1</label>
<description>Sprinkler Zone 1</description>
</channel-group>
<channel-group id="zone2" typeId="zone">
<label>Zone 2</label>
<description>Sprinkler Zone 2</description>
</channel-group>
<channel-group id="zone3" typeId="zone">
<label>Zone 3</label>
<description>Sprinkler Zone 3</description>
</channel-group>
<channel-group id="zone4" typeId="zone">
<label>Zone 4</label>
<description>Sprinkler Zone 4</description>
</channel-group>
<channel-group id="zone5" typeId="zone">
<label>Zone 5</label>
<description>Sprinkler Zone 5</description>
</channel-group>
<channel-group id="zone6" typeId="zone">
<label>Zone 6</label>
<description>Sprinkler Zone 6</description>
</channel-group>
<channel-group id="zone7" typeId="zone">
<label>Zone 7</label>
<description>Sprinkler Zone 7</description>
</channel-group>
<channel-group id="zone8" typeId="zone">
<label>Zone 8</label>
<description>Sprinkler Zone 8</description>
</channel-group>
<channel-group id="zone9" typeId="zone">
<label>Zone 9</label>
<description>Sprinkler Zone 9</description>
</channel-group>
<channel-group id="zone10" typeId="zone">
<label>Zone 10</label>
<description>Sprinkler Zone 10</description>
</channel-group>
<channel-group id="zone11" typeId="zone">
<label>Zone 11</label>
<description>Sprinkler Zone 11</description>
</channel-group>
<channel-group id="zone12" typeId="zone">
<label>Zone 12</label>
<description>Sprinkler Zone 12</description>
</channel-group>
<channel-group id="zone13" typeId="zone">
<label>Zone 13</label>
<description>Sprinkler Zone 13</description>
</channel-group>
<channel-group id="zone14" typeId="zone">
<label>Zone 14</label>
<description>Sprinkler Zone 14</description>
</channel-group>
<channel-group id="zone15" typeId="zone">
<label>Zone 15</label>
<description>Sprinkler Zone 15</description>
</channel-group>
<channel-group id="zone16" typeId="zone">
<label>Zone 16</label>
<description>Sprinkler Zone 16</description>
</channel-group>
<channel-group id="zone17" typeId="zone">
<label>Zone 17</label>
<description>Sprinkler Zone 17</description>
</channel-group>
<channel-group id="zone18" typeId="zone">
<label>Zone 18</label>
<description>Sprinkler Zone 18</description>
</channel-group>
<channel-group id="zone19" typeId="zone">
<label>Zone 19</label>
<description>Sprinkler Zone 19</description>
</channel-group>
<channel-group id="zone20" typeId="zone">
<label>Zone 20</label>
<description>Sprinkler Zone 20</description>
</channel-group>
<channel-group id="zone21" typeId="zone">
<label>Zone 21</label>
<description>Sprinkler Zone 21</description>
</channel-group>
<channel-group id="zone22" typeId="zone">
<label>Zone 22</label>
<description>Sprinkler Zone 22</description>
</channel-group>
<channel-group id="zone23" typeId="zone">
<label>Zone 23</label>
<description>Sprinkler Zone 23</description>
</channel-group>
<channel-group id="zone24" typeId="zone">
<label>Zone 24</label>
<description>Sprinkler Zone 24</description>
</channel-group>
<channel-group id="zone25" typeId="zone">
<label>Zone 25</label>
<description>Sprinkler Zone 25</description>
</channel-group>
<channel-group id="zone26" typeId="zone">
<label>Zone 26</label>
<description>Sprinkler Zone 26</description>
</channel-group>
<channel-group id="zone27" typeId="zone">
<label>Zone 27</label>
<description>Sprinkler Zone 27</description>
</channel-group>
<channel-group id="zone28" typeId="zone">
<label>Zone 28</label>
<description>Sprinkler Zone 28</description>
</channel-group>
<channel-group id="zone29" typeId="zone">
<label>Zone 29</label>
<description>Sprinkler Zone 29</description>
</channel-group>
<channel-group id="zone30" typeId="zone">
<label>Zone 30</label>
<description>Sprinkler Zone 30</description>
</channel-group>
<channel-group id="zone31" typeId="zone">
<label>Zone 31</label>
<description>Sprinkler Zone 31</description>
</channel-group>
<channel-group id="zone32" typeId="zone">
<label>Zone 32</label>
<description>Sprinkler Zone 32</description>
</channel-group>
<channel-group id="zone33" typeId="zone">
<label>Zone 33</label>
<description>Sprinkler Zone 33</description>
</channel-group>
<channel-group id="zone34" typeId="zone">
<label>Zone 34</label>
<description>Sprinkler Zone 34</description>
</channel-group>
<channel-group id="zone35" typeId="zone">
<label>Zone 35</label>
<description>Sprinkler Zone 35</description>
</channel-group>
<channel-group id="zone36" typeId="zone">
<label>Zone 36</label>
<description>Sprinkler Zone 36</description>
</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.
</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="local">
<label>Hydrawise Local Thing</label>
<description>Hydrawise local connected irrigation system</description>
<channel-groups>
<channel-group id="zone1" typeId="zone">
<label>Zone 1</label>
<description>Sprinkler Zone 1</description>
</channel-group>
<channel-group id="zone2" typeId="zone">
<label>Zone 2</label>
<description>Sprinkler Zone 2</description>
</channel-group>
<channel-group id="zone3" typeId="zone">
<label>Zone 3</label>
<description>Sprinkler Zone 3</description>
</channel-group>
<channel-group id="zone4" typeId="zone">
<label>Zone 4</label>
<description>Sprinkler Zone 4</description>
</channel-group>
<channel-group id="zone5" typeId="zone">
<label>Zone 5</label>
<description>Sprinkler Zone 5</description>
</channel-group>
<channel-group id="zone6" typeId="zone">
<label>Zone 6</label>
<description>Sprinkler Zone 6</description>
</channel-group>
<channel-group id="zone7" typeId="zone">
<label>Zone 7</label>
<description>Sprinkler Zone 7</description>
</channel-group>
<channel-group id="zone8" typeId="zone">
<label>Zone 8</label>
<description>Sprinkler Zone 8</description>
</channel-group>
<channel-group id="zone9" typeId="zone">
<label>Zone 9</label>
<description>Sprinkler Zone 9</description>
</channel-group>
<channel-group id="zone10" typeId="zone">
<label>Zone 10</label>
<description>Sprinkler Zone 10</description>
</channel-group>
<channel-group id="zone11" typeId="zone">
<label>Zone 11</label>
<description>Sprinkler Zone 11</description>
</channel-group>
<channel-group id="zone12" typeId="zone">
<label>Zone 12</label>
<description>Sprinkler Zone 12</description>
</channel-group>
<channel-group id="zone13" typeId="zone">
<label>Zone 13</label>
<description>Sprinkler Zone 13</description>
</channel-group>
<channel-group id="zone14" typeId="zone">
<label>Zone 14</label>
<description>Sprinkler Zone 14</description>
</channel-group>
<channel-group id="zone15" typeId="zone">
<label>Zone 15</label>
<description>Sprinkler Zone 15</description>
</channel-group>
<channel-group id="zone16" typeId="zone">
<label>Zone 16</label>
<description>Sprinkler Zone 16</description>
</channel-group>
<channel-group id="zone17" typeId="zone">
<label>Zone 17</label>
<description>Sprinkler Zone 17</description>
</channel-group>
<channel-group id="zone18" typeId="zone">
<label>Zone 18</label>
<description>Sprinkler Zone 18</description>
</channel-group>
<channel-group id="zone19" typeId="zone">
<label>Zone 19</label>
<description>Sprinkler Zone 19</description>
</channel-group>
<channel-group id="zone20" typeId="zone">
<label>Zone 20</label>
<description>Sprinkler Zone 20</description>
</channel-group>
<channel-group id="zone21" typeId="zone">
<label>Zone 21</label>
<description>Sprinkler Zone 21</description>
</channel-group>
<channel-group id="zone22" typeId="zone">
<label>Zone 22</label>
<description>Sprinkler Zone 22</description>
</channel-group>
<channel-group id="zone23" typeId="zone">
<label>Zone 23</label>
<description>Sprinkler Zone 23</description>
</channel-group>
<channel-group id="zone24" typeId="zone">
<label>Zone 24</label>
<description>Sprinkler Zone 24</description>
</channel-group>
<channel-group id="zone25" typeId="zone">
<label>Zone 25</label>
<description>Sprinkler Zone 25</description>
</channel-group>
<channel-group id="zone26" typeId="zone">
<label>Zone 26</label>
<description>Sprinkler Zone 26</description>
</channel-group>
<channel-group id="zone27" typeId="zone">
<label>Zone 27</label>
<description>Sprinkler Zone 27</description>
</channel-group>
<channel-group id="zone28" typeId="zone">
<label>Zone 28</label>
<description>Sprinkler Zone 28</description>
</channel-group>
<channel-group id="zone29" typeId="zone">
<label>Zone 29</label>
<description>Sprinkler Zone 29</description>
</channel-group>
<channel-group id="zone30" typeId="zone">
<label>Zone 30</label>
<description>Sprinkler Zone 30</description>
</channel-group>
<channel-group id="zone31" typeId="zone">
<label>Zone 31</label>
<description>Sprinkler Zone 31</description>
</channel-group>
<channel-group id="zone32" typeId="zone">
<label>Zone 32</label>
<description>Sprinkler Zone 32</description>
</channel-group>
<channel-group id="zone33" typeId="zone">
<label>Zone 33</label>
<description>Sprinkler Zone 33</description>
</channel-group>
<channel-group id="zone34" typeId="zone">
<label>Zone 34</label>
<description>Sprinkler Zone 34</description>
</channel-group>
<channel-group id="zone35" typeId="zone">
<label>Zone 35</label>
<description>Sprinkler Zone 35</description>
</channel-group>
<channel-group id="zone36" typeId="zone">
<label>Zone 36</label>
<description>Sprinkler Zone 36</description>
</channel-group>
</channel-groups>
<config-description>
<parameter name="host" type="text" required="true">
<label>Host</label>
<description>Host or IP address of local controller</description>
</parameter>
<parameter name="username" type="text" required="true">
<default>admin</default>
<label>User Name</label>
<description>User name for controller, usually "admin"</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>password</label>
<context>password</context>
<description>Password for local controller, found in the settings menu on the controller itself.</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>
</config-description>
</thing-type>
</thing:thing-descriptions>