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,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>