]> git.basschouten.com Git - openhab-addons.git/blob
4f1cbb26757ca3cbc90aac30fba599e873a1faac
[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.gardena.internal.handler;
14
15 import static org.openhab.binding.gardena.internal.GardenaBindingConstants.*;
16 import static org.openhab.binding.gardena.internal.GardenaSmartCommandName.*;
17
18 import java.time.ZoneId;
19 import java.time.ZonedDateTime;
20 import java.util.Calendar;
21 import java.util.Map;
22 import java.util.Map.Entry;
23
24 import org.apache.commons.lang.ObjectUtils;
25 import org.apache.commons.lang.StringUtils;
26 import org.openhab.binding.gardena.internal.GardenaSmart;
27 import org.openhab.binding.gardena.internal.GardenaSmartCommandName;
28 import org.openhab.binding.gardena.internal.GardenaSmartImpl;
29 import org.openhab.binding.gardena.internal.exception.GardenaDeviceNotFoundException;
30 import org.openhab.binding.gardena.internal.exception.GardenaException;
31 import org.openhab.binding.gardena.internal.model.Ability;
32 import org.openhab.binding.gardena.internal.model.Device;
33 import org.openhab.binding.gardena.internal.model.Setting;
34 import org.openhab.binding.gardena.internal.util.DateUtils;
35 import org.openhab.binding.gardena.internal.util.UidUtils;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.config.core.validation.ConfigValidationException;
38 import org.openhab.core.library.types.DateTimeType;
39 import org.openhab.core.library.types.DecimalType;
40 import org.openhab.core.library.types.OnOffType;
41 import org.openhab.core.library.types.StringType;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.binding.BaseThingHandler;
47 import org.openhab.core.types.Command;
48 import org.openhab.core.types.RefreshType;
49 import org.openhab.core.types.State;
50 import org.openhab.core.types.Type;
51 import org.openhab.core.types.UnDefType;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * The {@link GardenaThingHandler} is responsible for handling commands, which are sent to one of the channels.
57  *
58  * @author Gerhard Riegler - Initial contribution
59  */
60 public class GardenaThingHandler extends BaseThingHandler {
61
62     private final Logger logger = LoggerFactory.getLogger(GardenaThingHandler.class);
63     private final Calendar VALID_DATE_START = DateUtils.parseToCalendar("1970-01-02T00:00Z");
64
65     public GardenaThingHandler(Thing thing) {
66         super(thing);
67     }
68
69     @Override
70     public void initialize() {
71         try {
72             Device device = getDevice();
73             updateProperties(device);
74             updateSettings(device);
75             updateStatus(device);
76         } catch (GardenaException ex) {
77             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, ex.getMessage());
78         } catch (AccountHandlerNotAvailableException ex) {
79             // ignore
80         }
81     }
82
83     /**
84      * Updates the thing configuration from the Gardena device.
85      */
86     protected void updateSettings(Device device) throws GardenaException {
87         if (GardenaSmartImpl.DEVICE_CATEGORY_PUMP.equals(device.getCategory())) {
88             Configuration config = editConfiguration();
89
90             if (!equalsSetting(config, device, SETTING_LEAKAGE_DETECTION)
91                     || !equalsSetting(config, device, SETTING_OPERATION_MODE)
92                     || !equalsSetting(config, device, SETTING_TURN_ON_PRESSURE)) {
93                 config.put(SETTING_LEAKAGE_DETECTION, device.getSetting(SETTING_LEAKAGE_DETECTION).getValue());
94                 config.put(SETTING_OPERATION_MODE, device.getSetting(SETTING_OPERATION_MODE).getValue());
95                 config.put(SETTING_TURN_ON_PRESSURE,
96                         ObjectUtils.toString(device.getSetting(SETTING_TURN_ON_PRESSURE).getValue()));
97                 updateConfiguration(config);
98             }
99         }
100     }
101
102     private boolean equalsSetting(Configuration config, Device device, String key) throws GardenaException {
103         return config.get(key) != null
104                 && config.get(key).equals(ObjectUtils.toString(device.getSetting(key).getValue()));
105     }
106
107     /**
108      * Updates the thing properties from the Gardena device.
109      */
110     protected void updateProperties(Device device) throws GardenaException {
111         Map<String, String> properties = editProperties();
112         Ability deviceInfo = device.getAbility(ABILITY_DEVICE_INFO);
113         setProperty(properties, deviceInfo, PROPERTY_MANUFACTURER);
114         setProperty(properties, deviceInfo, PROPERTY_PRODUCT);
115         setProperty(properties, deviceInfo, PROPERTY_SERIALNUMBER);
116         setProperty(properties, deviceInfo, PROPERTY_SGTIN);
117         setProperty(properties, deviceInfo, PROPERTY_VERSION);
118         setProperty(properties, deviceInfo, PROPERTY_CATEGORY);
119         updateProperties(properties);
120     }
121
122     private void setProperty(Map<String, String> properties, Ability deviceInfo, String propertyName) {
123         try {
124             properties.put(propertyName, deviceInfo.getProperty(propertyName).getValueAsString());
125         } catch (GardenaException ex) {
126             logger.debug("Ignoring missing device property {}", propertyName);
127         }
128     }
129
130     @Override
131     public void channelLinked(ChannelUID channelUID) {
132         try {
133             updateChannel(channelUID);
134         } catch (GardenaDeviceNotFoundException | AccountHandlerNotAvailableException ex) {
135             logger.debug("{}", ex.getMessage(), ex);
136         } catch (GardenaException ex) {
137             logger.error("{}", ex.getMessage(), ex);
138         }
139     }
140
141     /**
142      * Updates the channel from the Gardena device.
143      */
144     protected void updateChannel(ChannelUID channelUID) throws GardenaException, AccountHandlerNotAvailableException {
145         Device device = getDevice();
146         State state = convertToState(device, channelUID);
147         if (state != null) {
148             updateState(channelUID, state);
149         }
150     }
151
152     /**
153      * Converts a Gardena property value to a openHAB state.
154      */
155     private State convertToState(Device device, ChannelUID channelUID) throws GardenaException {
156         String abilityName = channelUID.getGroupId();
157         String propertyName = channelUID.getIdWithoutGroup();
158
159         try {
160             String value = device.getAbility(abilityName).getProperty(propertyName).getValueAsString();
161
162             if (StringUtils.trimToNull(value) == null || StringUtils.equals(value, "N/A")) {
163                 return UnDefType.NULL;
164             }
165
166             switch (getThing().getChannel(channelUID.getId()).getAcceptedItemType()) {
167                 case "String":
168                     return new StringType(value);
169                 case "Number":
170                     if (ABILITY_RADIO.equals(abilityName) && PROPERTY_STATE.equals(propertyName)) {
171                         switch (value) {
172                             case "poor":
173                                 return new DecimalType(1);
174                             case "good":
175                                 return new DecimalType(2);
176                             case "excellent":
177                                 return new DecimalType(4);
178                             default:
179                                 return UnDefType.NULL;
180                         }
181                     }
182                     return new DecimalType(value);
183                 case "Switch":
184                     return Boolean.TRUE.toString().equalsIgnoreCase(value) || "on".equalsIgnoreCase(value)
185                             ? OnOffType.ON
186                             : OnOffType.OFF;
187                 case "DateTime":
188                     Calendar cal = DateUtils.parseToCalendar(value);
189                     if (cal != null && !cal.before(VALID_DATE_START)) {
190                         return new DateTimeType(ZonedDateTime.ofInstant(cal.toInstant(), ZoneId.systemDefault()));
191                     } else {
192                         return UnDefType.NULL;
193                     }
194             }
195         } catch (GardenaException e) {
196             logger.warn("Channel '{}' cannot be updated as device does not contain property '{}:{}'", channelUID,
197                     abilityName, propertyName);
198         }
199         return null;
200     }
201
202     /**
203      * Converts an openHAB type to a Gardena command property.
204      */
205     private Object convertFromType(Type type) {
206         if (type instanceof OnOffType) {
207             return type == OnOffType.ON ? Boolean.TRUE : Boolean.FALSE;
208         } else if (type instanceof DecimalType) {
209             return ((DecimalType) type).intValue();
210         } else if (type instanceof StringType) {
211             return ((StringType) type).toFullString();
212         }
213         return null;
214     }
215
216     @Override
217     public void handleCommand(ChannelUID channelUID, Command command) {
218         try {
219             GardenaSmartCommandName commandName = getCommandName(channelUID);
220             logger.debug("Received Gardena command: {}", commandName);
221
222             if (RefreshType.REFRESH == command) {
223                 logger.debug("Refreshing channel '{}'", channelUID);
224                 if (commandName != null && commandName.toString().startsWith("MEASURE_")) {
225                     getGardenaSmart().sendCommand(getDevice(), commandName, null);
226                 } else {
227                     updateChannel(channelUID);
228                 }
229             } else if (commandName != null) {
230                 getGardenaSmart().sendCommand(getDevice(), commandName, convertFromType(command));
231             }
232         } catch (AccountHandlerNotAvailableException | GardenaDeviceNotFoundException ex) {
233             // ignore
234         } catch (Exception ex) {
235             logger.warn("{}", ex.getMessage(), ex);
236         }
237     }
238
239     /**
240      * Returns the Gardena command from the channel.
241      */
242     private GardenaSmartCommandName getCommandName(ChannelUID channelUID) {
243         switch (channelUID.getId()) {
244             case "mower#park_until_further_notice":
245                 return PARK_UNTIL_FURTHER_NOTICE;
246             case "mower#park_until_next_timer":
247                 return PARK_UNTIL_NEXT_TIMER;
248             case "mower#start_override_timer":
249                 return START_OVERRIDE_TIMER;
250             case "mower#start_resume_schedule":
251                 return START_RESUME_SCHEDULE;
252             case "mower#duration_property":
253                 return DURATION_PROPERTY;
254
255             case "ambient_temperature#temperature":
256                 return MEASURE_AMBIENT_TEMPERATURE;
257             case "soil_temperature#temperature":
258                 return MEASURE_SOIL_TEMPERATURE;
259             case "humidity#humidity":
260                 return MEASURE_SOIL_HUMIDITY;
261             case "light#light":
262                 return MEASURE_LIGHT;
263
264             case "outlet#button_manual_override_time":
265                 return OUTLET_MANUAL_OVERRIDE_TIME;
266             case "outlet#valve_open":
267                 return OUTLET_VALVE;
268
269             case "power#power_timer":
270                 return POWER_TIMER;
271             case "watering#watering_timer_1":
272                 return WATERING_TIMER_VALVE_1;
273             case "watering#watering_timer_2":
274                 return WATERING_TIMER_VALVE_2;
275             case "watering#watering_timer_3":
276                 return WATERING_TIMER_VALVE_3;
277             case "watering#watering_timer_4":
278                 return WATERING_TIMER_VALVE_4;
279             case "watering#watering_timer_5":
280                 return WATERING_TIMER_VALVE_5;
281             case "watering#watering_timer_6":
282                 return WATERING_TIMER_VALVE_6;
283
284             case "manual_watering#manual_watering_timer":
285                 return PUMP_MANUAL_WATERING_TIMER;
286
287             default:
288                 return null;
289         }
290     }
291
292     /**
293      * Updates the thing status based on the Gardena device status.
294      */
295     protected void updateStatus(Device device) {
296         String connectionStatus = "";
297         try {
298             connectionStatus = device.getAbility(ABILITY_RADIO).getProperty(PROPERTY_CONNECTION_STATUS)
299                     .getValueAsString();
300         } catch (GardenaException ex) {
301             // ignore, device has no connection status property
302         }
303
304         boolean isUnreach = PROPERTY_CONNECTION_STATUS_UNREACH_VALUE.equals(connectionStatus);
305
306         ThingStatus oldStatus = thing.getStatus();
307         ThingStatus newStatus = ThingStatus.ONLINE;
308         ThingStatusDetail newDetail = ThingStatusDetail.NONE;
309
310         if (isUnreach) {
311             newStatus = ThingStatus.OFFLINE;
312             newDetail = ThingStatusDetail.COMMUNICATION_ERROR;
313         } else if (!device.isConfigurationSynchronized()) {
314             newStatus = thing.getStatus();
315             newDetail = ThingStatusDetail.CONFIGURATION_PENDING;
316         }
317
318         if (oldStatus != newStatus || thing.getStatusInfo().getStatusDetail() != newDetail) {
319             updateStatus(newStatus, newDetail);
320         }
321     }
322
323     @Override
324     public void handleConfigurationUpdate(Map<String, Object> configurationParameters)
325             throws ConfigValidationException {
326         validateConfigurationParameters(configurationParameters);
327
328         try {
329             GardenaSmart gardena = getGardenaSmart();
330             Device device = gardena.getDevice(UidUtils.getGardenaDeviceId(getThing()));
331
332             for (Entry<String, Object> configurationParmeter : configurationParameters.entrySet()) {
333                 String key = configurationParmeter.getKey();
334                 Object newValue = configurationParmeter.getValue();
335                 if (newValue != null && SETTING_TURN_ON_PRESSURE.equals(key)) {
336                     newValue = new Double((String) newValue);
337                 }
338
339                 Setting setting = device.getSetting(key);
340                 if (ObjectUtils.notEqual(setting.getValue(), newValue)) {
341                     gardena.sendSetting(setting, newValue);
342                     setting.setValue(newValue);
343                 }
344             }
345             updateSettings(device);
346         } catch (GardenaException | AccountHandlerNotAvailableException ex) {
347             logger.warn("Error setting thing properties: {}", ex.getMessage(), ex);
348         }
349     }
350
351     /**
352      * Returns the Gardena device for this ThingHandler.
353      */
354     private Device getDevice() throws GardenaException, AccountHandlerNotAvailableException {
355         return getGardenaSmart().getDevice(UidUtils.getGardenaDeviceId(getThing()));
356     }
357
358     /**
359      * Returns the Gardena Smart Home implementation if the bridge is available.
360      */
361     private GardenaSmart getGardenaSmart() throws AccountHandlerNotAvailableException {
362         if (getBridge() == null || getBridge().getHandler() == null
363                 || ((GardenaAccountHandler) getBridge().getHandler()).getGardenaSmart() == null) {
364             if (thing.getStatus() != ThingStatus.INITIALIZING) {
365                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR);
366             }
367             throw new AccountHandlerNotAvailableException("Gardena AccountHandler not yet available!");
368         }
369
370         return ((GardenaAccountHandler) getBridge().getHandler()).getGardenaSmart();
371     }
372 }