2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.hydrawise.internal;
15 import static org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants.*;
17 import java.util.List;
18 import java.util.Optional;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
22 import org.apache.commons.lang.StringUtils;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
28 import org.openhab.binding.hydrawise.internal.api.HydrawiseCloudApiClient;
29 import org.openhab.binding.hydrawise.internal.api.HydrawiseCommandException;
30 import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
31 import org.openhab.binding.hydrawise.internal.api.model.Controller;
32 import org.openhab.binding.hydrawise.internal.api.model.CustomerDetailsResponse;
33 import org.openhab.binding.hydrawise.internal.api.model.Forecast;
34 import org.openhab.binding.hydrawise.internal.api.model.Relay;
35 import org.openhab.binding.hydrawise.internal.api.model.StatusScheduleResponse;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.library.unit.ImperialUnits;
41 import org.openhab.core.library.unit.SIUnits;
42 import org.openhab.core.thing.Thing;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * The {@link HydrawiseCloudHandler} is responsible for handling commands, which are
48 * sent to one of the channels.
50 * @author Dan Cunningham - Initial contribution
53 public class HydrawiseCloudHandler extends HydrawiseHandler {
57 private static final Pattern TEMPERATURE_PATTERN = Pattern.compile("^(\\d{1,3}.?\\d?)\\s([C,F])");
61 private static final Pattern WIND_SPEED_PATTERN = Pattern.compile("^(\\d{1,3})\\s([a-z]{3})");
62 private final Logger logger = LoggerFactory.getLogger(HydrawiseCloudHandler.class);
63 private HydrawiseCloudApiClient client;
64 private int controllerId;
66 public HydrawiseCloudHandler(Thing thing, HttpClient httpClient) {
68 this.client = new HydrawiseCloudApiClient(httpClient);
72 protected void configure()
73 throws NotConfiguredException, HydrawiseConnectionException, HydrawiseAuthenticationException {
74 HydrawiseCloudConfiguration configuration = getConfig().as(HydrawiseCloudConfiguration.class);
76 this.refresh = Math.max(configuration.refresh, MIN_REFRESH_SECONDS);
78 client.setApiKey(configuration.apiKey);
80 CustomerDetailsResponse customerDetails = client.getCustomerDetails();
82 List<Controller> controllers = customerDetails.controllers;
83 if (controllers.isEmpty()) {
84 throw new NotConfiguredException("No controllers found on account");
87 Controller controller = null;
88 // try and use ID from user configuration
89 if (configuration.controllerId != null) {
90 controller = getController(configuration.controllerId.intValue(), controllers);
91 if (controller == null) {
92 throw new NotConfiguredException("No controller found for id " + configuration.controllerId);
95 // try and use ID from saved property
96 String controllerId = getThing().getProperties().get(PROPERTY_CONTROLLER_ID);
97 if (StringUtils.isNotBlank(controllerId)) {
99 controller = getController(Integer.parseInt(controllerId), controllers);
101 } catch (NumberFormatException e) {
102 logger.debug("Can not parse property vaue {}", controllerId);
105 // use current controller ID
106 if (controller == null) {
107 controller = getController(customerDetails.controllerId, controllers);
111 if (controller == null) {
112 throw new NotConfiguredException("No controller found");
115 controllerId = controller.controllerId.intValue();
116 updateControllerProperties(controller);
117 logger.debug("Controller id {}", controllerId);
121 * Poll the controller for updates.
124 protected void pollController() throws HydrawiseConnectionException, HydrawiseAuthenticationException {
125 List<Controller> controllers = client.getCustomerDetails().controllers;
126 Controller controller = getController(controllerId, controllers);
127 if (controller != null && !controller.online) {
128 throw new HydrawiseConnectionException("Controller is offline");
130 StatusScheduleResponse status = client.getStatusSchedule(controllerId);
131 updateSensors(status);
132 updateForecast(status);
137 protected void sendRunCommand(int seconds, @Nullable Relay relay)
138 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
140 client.runRelay(seconds, relay.relayId);
145 protected void sendRunCommand(@Nullable Relay relay)
146 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
148 client.runRelay(relay.relayId);
153 protected void sendStopCommand(@Nullable Relay relay)
154 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
156 client.stopRelay(relay.relayId);
161 protected void sendRunAllCommand()
162 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
163 client.runAllRelays(controllerId);
167 protected void sendRunAllCommand(int seconds)
168 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
169 client.runAllRelays(seconds, controllerId);
173 protected void sendStopAllCommand()
174 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
175 client.stopAllRelays(controllerId);
178 private void updateSensors(StatusScheduleResponse status) {
179 status.sensors.forEach(sensor -> {
180 String group = "sensor" + sensor.input;
181 updateGroupState(group, CHANNEL_SENSOR_MODE, new DecimalType(sensor.type));
182 updateGroupState(group, CHANNEL_SENSOR_NAME, new StringType(sensor.name));
183 updateGroupState(group, CHANNEL_SENSOR_OFFTIMER, new DecimalType(sensor.offtimer));
184 updateGroupState(group, CHANNEL_SENSOR_TIMER, new DecimalType(sensor.timer));
185 // Some fields are missing depending on sensor type.
186 if (sensor.offlevel != null) {
187 updateGroupState(group, CHANNEL_SENSOR_OFFLEVEL, new DecimalType(sensor.offlevel));
189 if (sensor.active != null) {
190 updateGroupState(group, CHANNEL_SENSOR_ACTIVE, sensor.active > 0 ? OnOffType.ON : OnOffType.OFF);
195 private void updateForecast(StatusScheduleResponse status) {
197 for (Forecast forecast : status.forecast) {
198 String group = "forecast" + (i++);
199 updateGroupState(group, CHANNEL_FORECAST_CONDITIONS, new StringType(forecast.conditions));
200 updateGroupState(group, CHANNEL_FORECAST_DAY, new StringType(forecast.day));
201 updateGroupState(group, CHANNEL_FORECAST_HUMIDITY, new DecimalType(forecast.humidity));
202 updateTemperature(forecast.tempHi, group, CHANNEL_FORECAST_TEMPERATURE_HIGH);
203 updateTemperature(forecast.tempLo, group, CHANNEL_FORECAST_TEMPERATURE_LOW);
204 updateWindspeed(forecast.wind, group, CHANNEL_FORECAST_WIND);
208 private void updateTemperature(String tempString, String group, String channel) {
209 Matcher matcher = TEMPERATURE_PATTERN.matcher(tempString);
210 if (matcher.matches()) {
212 updateGroupState(group, channel, new QuantityType<>(Double.valueOf(matcher.group(1)),
213 "C".equals(matcher.group(2)) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT));
214 } catch (NumberFormatException e) {
215 logger.debug("Could not parse temperature string {} ", tempString);
220 private void updateWindspeed(String windString, String group, String channel) {
221 Matcher matcher = WIND_SPEED_PATTERN.matcher(windString);
222 if (matcher.matches()) {
224 updateGroupState(group, channel, new QuantityType<>(Integer.parseInt(matcher.group(1)),
225 "kph".equals(matcher.group(2)) ? SIUnits.KILOMETRE_PER_HOUR : ImperialUnits.MILES_PER_HOUR));
226 } catch (NumberFormatException e) {
227 logger.debug("Could not parse wind string {} ", windString);
232 private void updateControllerProperties(Controller controller) {
233 getThing().setProperty(PROPERTY_CONTROLLER_ID, String.valueOf(controller.controllerId));
234 getThing().setProperty(PROPERTY_NAME, controller.name);
235 getThing().setProperty(PROPERTY_DESCRIPTION, controller.description);
236 getThing().setProperty(PROPERTY_LOCATION, controller.latitude + "," + controller.longitude);
237 getThing().setProperty(PROPERTY_ADDRESS, controller.address);
240 private @Nullable Controller getController(int controllerId, List<Controller> controllers) {
241 Optional<@NonNull Controller> optionalController = controllers.stream()
242 .filter(c -> controllerId == c.controllerId.intValue()).findAny();
243 return optionalController.isPresent() ? optionalController.get() : null;