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.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.openhab.binding.hydrawise.internal.api.HydrawiseAuthenticationException;
27 import org.openhab.binding.hydrawise.internal.api.HydrawiseCloudApiClient;
28 import org.openhab.binding.hydrawise.internal.api.HydrawiseCommandException;
29 import org.openhab.binding.hydrawise.internal.api.HydrawiseConnectionException;
30 import org.openhab.binding.hydrawise.internal.api.model.Controller;
31 import org.openhab.binding.hydrawise.internal.api.model.CustomerDetailsResponse;
32 import org.openhab.binding.hydrawise.internal.api.model.Forecast;
33 import org.openhab.binding.hydrawise.internal.api.model.Relay;
34 import org.openhab.binding.hydrawise.internal.api.model.StatusScheduleResponse;
35 import org.openhab.core.library.types.DecimalType;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.library.types.QuantityType;
38 import org.openhab.core.library.types.StringType;
39 import org.openhab.core.library.unit.ImperialUnits;
40 import org.openhab.core.library.unit.SIUnits;
41 import org.openhab.core.thing.Thing;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * The {@link HydrawiseCloudHandler} is responsible for handling commands, which are
47 * sent to one of the channels.
49 * @author Dan Cunningham - Initial contribution
52 public class HydrawiseCloudHandler extends HydrawiseHandler {
56 private static final Pattern TEMPERATURE_PATTERN = Pattern.compile("^(\\d{1,3}.?\\d?)\\s([C,F])");
60 private static final Pattern WIND_SPEED_PATTERN = Pattern.compile("^(\\d{1,3})\\s([a-z]{3})");
61 private final Logger logger = LoggerFactory.getLogger(HydrawiseCloudHandler.class);
62 private HydrawiseCloudApiClient client;
63 private int controllerId;
65 public HydrawiseCloudHandler(Thing thing, HttpClient httpClient) {
67 this.client = new HydrawiseCloudApiClient(httpClient);
71 protected void configure()
72 throws NotConfiguredException, HydrawiseConnectionException, HydrawiseAuthenticationException {
73 HydrawiseCloudConfiguration configuration = getConfig().as(HydrawiseCloudConfiguration.class);
75 this.refresh = Math.max(configuration.refresh, MIN_REFRESH_SECONDS);
77 client.setApiKey(configuration.apiKey);
79 CustomerDetailsResponse customerDetails = client.getCustomerDetails();
81 List<Controller> controllers = customerDetails.controllers;
82 if (controllers.isEmpty()) {
83 throw new NotConfiguredException("No controllers found on account");
86 Controller controller = null;
87 // try and use ID from user configuration
88 if (configuration.controllerId != null) {
89 controller = getController(configuration.controllerId.intValue(), controllers);
90 if (controller == null) {
91 throw new NotConfiguredException("No controller found for id " + configuration.controllerId);
94 // try and use ID from saved property
95 String controllerId = getThing().getProperties().get(PROPERTY_CONTROLLER_ID);
96 if (controllerId != null && !controllerId.isBlank()) {
98 controller = getController(Integer.parseInt(controllerId), controllers);
99 } catch (NumberFormatException e) {
100 logger.debug("Can not parse property vaue {}", controllerId);
103 // use current controller ID
104 if (controller == null) {
105 controller = getController(customerDetails.controllerId, controllers);
109 if (controller == null) {
110 throw new NotConfiguredException("No controller found");
113 controllerId = controller.controllerId.intValue();
114 updateControllerProperties(controller);
115 logger.debug("Controller id {}", controllerId);
119 * Poll the controller for updates.
122 protected void pollController() throws HydrawiseConnectionException, HydrawiseAuthenticationException {
123 List<Controller> controllers = client.getCustomerDetails().controllers;
124 Controller controller = getController(controllerId, controllers);
125 if (controller != null && !controller.online) {
126 throw new HydrawiseConnectionException("Controller is offline");
128 StatusScheduleResponse status = client.getStatusSchedule(controllerId);
129 updateSensors(status);
130 updateForecast(status);
135 protected void sendRunCommand(int seconds, @Nullable Relay relay)
136 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
138 client.runRelay(seconds, relay.relayId);
143 protected void sendRunCommand(@Nullable Relay relay)
144 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
146 client.runRelay(relay.relayId);
151 protected void sendStopCommand(@Nullable Relay relay)
152 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
154 client.stopRelay(relay.relayId);
159 protected void sendRunAllCommand()
160 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
161 client.runAllRelays(controllerId);
165 protected void sendRunAllCommand(int seconds)
166 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
167 client.runAllRelays(seconds, controllerId);
171 protected void sendStopAllCommand()
172 throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
173 client.stopAllRelays(controllerId);
176 private void updateSensors(StatusScheduleResponse status) {
177 status.sensors.forEach(sensor -> {
178 String group = "sensor" + sensor.input;
179 updateGroupState(group, CHANNEL_SENSOR_MODE, new DecimalType(sensor.type));
180 updateGroupState(group, CHANNEL_SENSOR_NAME, new StringType(sensor.name));
181 updateGroupState(group, CHANNEL_SENSOR_OFFTIMER, new DecimalType(sensor.offtimer));
182 updateGroupState(group, CHANNEL_SENSOR_TIMER, new DecimalType(sensor.timer));
183 // Some fields are missing depending on sensor type.
184 if (sensor.offlevel != null) {
185 updateGroupState(group, CHANNEL_SENSOR_OFFLEVEL, new DecimalType(sensor.offlevel));
187 if (sensor.active != null) {
188 updateGroupState(group, CHANNEL_SENSOR_ACTIVE, sensor.active > 0 ? OnOffType.ON : OnOffType.OFF);
193 private void updateForecast(StatusScheduleResponse status) {
195 for (Forecast forecast : status.forecast) {
196 String group = "forecast" + (i++);
197 updateGroupState(group, CHANNEL_FORECAST_CONDITIONS, new StringType(forecast.conditions));
198 updateGroupState(group, CHANNEL_FORECAST_DAY, new StringType(forecast.day));
199 updateGroupState(group, CHANNEL_FORECAST_HUMIDITY, new DecimalType(forecast.humidity));
200 updateTemperature(forecast.tempHi, group, CHANNEL_FORECAST_TEMPERATURE_HIGH);
201 updateTemperature(forecast.tempLo, group, CHANNEL_FORECAST_TEMPERATURE_LOW);
202 updateWindspeed(forecast.wind, group, CHANNEL_FORECAST_WIND);
206 private void updateTemperature(String tempString, String group, String channel) {
207 Matcher matcher = TEMPERATURE_PATTERN.matcher(tempString);
208 if (matcher.matches()) {
210 updateGroupState(group, channel, new QuantityType<>(Double.valueOf(matcher.group(1)),
211 "C".equals(matcher.group(2)) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT));
212 } catch (NumberFormatException e) {
213 logger.debug("Could not parse temperature string {} ", tempString);
218 private void updateWindspeed(String windString, String group, String channel) {
219 Matcher matcher = WIND_SPEED_PATTERN.matcher(windString);
220 if (matcher.matches()) {
222 updateGroupState(group, channel, new QuantityType<>(Integer.parseInt(matcher.group(1)),
223 "kph".equals(matcher.group(2)) ? SIUnits.KILOMETRE_PER_HOUR : ImperialUnits.MILES_PER_HOUR));
224 } catch (NumberFormatException e) {
225 logger.debug("Could not parse wind string {} ", windString);
230 private void updateControllerProperties(Controller controller) {
231 getThing().setProperty(PROPERTY_CONTROLLER_ID, String.valueOf(controller.controllerId));
232 getThing().setProperty(PROPERTY_NAME, controller.name);
233 getThing().setProperty(PROPERTY_DESCRIPTION, controller.description);
234 getThing().setProperty(PROPERTY_LOCATION, controller.latitude + "," + controller.longitude);
235 getThing().setProperty(PROPERTY_ADDRESS, controller.address);
238 private @Nullable Controller getController(int controllerId, List<Controller> controllers) {
239 Optional<@NonNull Controller> optionalController = controllers.stream()
240 .filter(c -> controllerId == c.controllerId.intValue()).findAny();
241 return optionalController.isPresent() ? optionalController.get() : null;