]> git.basschouten.com Git - openhab-addons.git/blob
fccc6a21997a3a0df832094becf8c67e1bf40e5e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.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;
44
45 /**
46  * The {@link HydrawiseCloudHandler} is responsible for handling commands, which are
47  * sent to one of the channels.
48  *
49  * @author Dan Cunningham - Initial contribution
50  */
51 @NonNullByDefault
52 public class HydrawiseCloudHandler extends HydrawiseHandler {
53     /**
54      * 74.2 F
55      */
56     private static final Pattern TEMPERATURE_PATTERN = Pattern.compile("^(\\d{1,3}.?\\d?)\\s([C,F])");
57     /**
58      * 9 mph
59      */
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;
64
65     public HydrawiseCloudHandler(Thing thing, HttpClient httpClient) {
66         super(thing);
67         this.client = new HydrawiseCloudApiClient(httpClient);
68     }
69
70     @Override
71     protected void configure()
72             throws NotConfiguredException, HydrawiseConnectionException, HydrawiseAuthenticationException {
73         HydrawiseCloudConfiguration configuration = getConfig().as(HydrawiseCloudConfiguration.class);
74
75         this.refresh = Math.max(configuration.refresh, MIN_REFRESH_SECONDS);
76
77         client.setApiKey(configuration.apiKey);
78
79         CustomerDetailsResponse customerDetails = client.getCustomerDetails();
80
81         List<Controller> controllers = customerDetails.controllers;
82         if (controllers.isEmpty()) {
83             throw new NotConfiguredException("No controllers found on account");
84         }
85
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);
92             }
93         } else {
94             // try and use ID from saved property
95             String controllerId = getThing().getProperties().get(PROPERTY_CONTROLLER_ID);
96             if (controllerId != null && !controllerId.isBlank()) {
97                 try {
98                     controller = getController(Integer.parseInt(controllerId), controllers);
99                 } catch (NumberFormatException e) {
100                     logger.debug("Can not parse property vaue {}", controllerId);
101                 }
102             }
103             // use current controller ID
104             if (controller == null) {
105                 controller = getController(customerDetails.controllerId, controllers);
106             }
107         }
108
109         if (controller == null) {
110             throw new NotConfiguredException("No controller found");
111         }
112
113         controllerId = controller.controllerId.intValue();
114         updateControllerProperties(controller);
115         logger.debug("Controller id {}", controllerId);
116     }
117
118     /**
119      * Poll the controller for updates.
120      */
121     @Override
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");
127         }
128         StatusScheduleResponse status = client.getStatusSchedule(controllerId);
129         updateSensors(status);
130         updateForecast(status);
131         updateZones(status);
132     }
133
134     @Override
135     protected void sendRunCommand(int seconds, @Nullable Relay relay)
136             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
137         if (relay != null) {
138             client.runRelay(seconds, relay.relayId);
139         }
140     }
141
142     @Override
143     protected void sendRunCommand(@Nullable Relay relay)
144             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
145         if (relay != null) {
146             client.runRelay(relay.relayId);
147         }
148     }
149
150     @Override
151     protected void sendStopCommand(@Nullable Relay relay)
152             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
153         if (relay != null) {
154             client.stopRelay(relay.relayId);
155         }
156     }
157
158     @Override
159     protected void sendRunAllCommand()
160             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
161         client.runAllRelays(controllerId);
162     }
163
164     @Override
165     protected void sendRunAllCommand(int seconds)
166             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
167         client.runAllRelays(seconds, controllerId);
168     }
169
170     @Override
171     protected void sendStopAllCommand()
172             throws HydrawiseCommandException, HydrawiseConnectionException, HydrawiseAuthenticationException {
173         client.stopAllRelays(controllerId);
174     }
175
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));
186             }
187             if (sensor.active != null) {
188                 updateGroupState(group, CHANNEL_SENSOR_ACTIVE, sensor.active > 0 ? OnOffType.ON : OnOffType.OFF);
189             }
190         });
191     }
192
193     private void updateForecast(StatusScheduleResponse status) {
194         int i = 1;
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);
203         }
204     }
205
206     private void updateTemperature(String tempString, String group, String channel) {
207         Matcher matcher = TEMPERATURE_PATTERN.matcher(tempString);
208         if (matcher.matches()) {
209             try {
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);
214             }
215         }
216     }
217
218     private void updateWindspeed(String windString, String group, String channel) {
219         Matcher matcher = WIND_SPEED_PATTERN.matcher(windString);
220         if (matcher.matches()) {
221             try {
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);
226             }
227         }
228     }
229
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);
236     }
237
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;
242     }
243 }