]> git.basschouten.com Git - openhab-addons.git/blob
12b6a0358b21cdd93e70704e0e3aeee6aea602c6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.hydrawise.internal;
14
15 import static org.openhab.binding.hydrawise.internal.HydrawiseBindingConstants.*;
16
17 import java.util.List;
18 import java.util.Optional;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
21
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;
45
46 /**
47  * The {@link HydrawiseCloudHandler} is responsible for handling commands, which are
48  * sent to one of the channels.
49  *
50  * @author Dan Cunningham - Initial contribution
51  */
52 @NonNullByDefault
53 public class HydrawiseCloudHandler extends HydrawiseHandler {
54     /**
55      * 74.2 F
56      */
57     private static final Pattern TEMPERATURE_PATTERN = Pattern.compile("^(\\d{1,3}.?\\d?)\\s([C,F])");
58     /**
59      * 9 mph
60      */
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;
65
66     public HydrawiseCloudHandler(Thing thing, HttpClient httpClient) {
67         super(thing);
68         this.client = new HydrawiseCloudApiClient(httpClient);
69     }
70
71     @Override
72     protected void configure()
73             throws NotConfiguredException, HydrawiseConnectionException, HydrawiseAuthenticationException {
74         HydrawiseCloudConfiguration configuration = getConfig().as(HydrawiseCloudConfiguration.class);
75
76         this.refresh = Math.max(configuration.refresh, MIN_REFRESH_SECONDS);
77
78         client.setApiKey(configuration.apiKey);
79
80         CustomerDetailsResponse customerDetails = client.getCustomerDetails();
81
82         List<Controller> controllers = customerDetails.controllers;
83         if (controllers.isEmpty()) {
84             throw new NotConfiguredException("No controllers found on account");
85         }
86
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);
93             }
94         } else {
95             // try and use ID from saved property
96             String controllerId = getThing().getProperties().get(PROPERTY_CONTROLLER_ID);
97             if (StringUtils.isNotBlank(controllerId)) {
98                 try {
99                     controller = getController(Integer.parseInt(controllerId), controllers);
100
101                 } catch (NumberFormatException e) {
102                     logger.debug("Can not parse property vaue {}", controllerId);
103                 }
104             }
105             // use current controller ID
106             if (controller == null) {
107                 controller = getController(customerDetails.controllerId, controllers);
108             }
109         }
110
111         if (controller == null) {
112             throw new NotConfiguredException("No controller found");
113         }
114
115         controllerId = controller.controllerId.intValue();
116         updateControllerProperties(controller);
117         logger.debug("Controller id {}", controllerId);
118     }
119
120     /**
121      * Poll the controller for updates.
122      */
123     @Override
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");
129         }
130         StatusScheduleResponse status = client.getStatusSchedule(controllerId);
131         updateSensors(status);
132         updateForecast(status);
133         updateZones(status);
134     }
135
136     @Override
137     protected void sendRunCommand(int seconds, @Nullable Relay relay)
138             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
139         if (relay != null) {
140             client.runRelay(seconds, relay.relayId);
141         }
142     }
143
144     @Override
145     protected void sendRunCommand(@Nullable Relay relay)
146             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
147         if (relay != null) {
148             client.runRelay(relay.relayId);
149         }
150     }
151
152     @Override
153     protected void sendStopCommand(@Nullable Relay relay)
154             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
155         if (relay != null) {
156             client.stopRelay(relay.relayId);
157         }
158     }
159
160     @Override
161     protected void sendRunAllCommand()
162             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
163         client.runAllRelays(controllerId);
164     }
165
166     @Override
167     protected void sendRunAllCommand(int seconds)
168             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
169         client.runAllRelays(seconds, controllerId);
170     }
171
172     @Override
173     protected void sendStopAllCommand()
174             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
175         client.stopAllRelays(controllerId);
176     }
177
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));
188             }
189             if (sensor.active != null) {
190                 updateGroupState(group, CHANNEL_SENSOR_ACTIVE, sensor.active > 0 ? OnOffType.ON : OnOffType.OFF);
191             }
192         });
193     }
194
195     private void updateForecast(StatusScheduleResponse status) {
196         int i = 1;
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);
205         }
206     }
207
208     private void updateTemperature(String tempString, String group, String channel) {
209         Matcher matcher = TEMPERATURE_PATTERN.matcher(tempString);
210         if (matcher.matches()) {
211             try {
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);
216             }
217         }
218     }
219
220     private void updateWindspeed(String windString, String group, String channel) {
221         Matcher matcher = WIND_SPEED_PATTERN.matcher(windString);
222         if (matcher.matches()) {
223             try {
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);
228             }
229         }
230     }
231
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);
238     }
239
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;
244     }
245 }