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.gardena.internal.handler;
15 import static org.openhab.binding.gardena.internal.GardenaBindingConstants.*;
16 import static org.openhab.binding.gardena.internal.GardenaSmartCommandName.*;
18 import java.time.ZoneId;
19 import java.time.ZonedDateTime;
20 import java.util.Calendar;
22 import java.util.Map.Entry;
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;
56 * The {@link GardenaThingHandler} is responsible for handling commands, which are sent to one of the channels.
58 * @author Gerhard Riegler - Initial contribution
60 public class GardenaThingHandler extends BaseThingHandler {
62 private final Logger logger = LoggerFactory.getLogger(GardenaThingHandler.class);
63 private final Calendar VALID_DATE_START = DateUtils.parseToCalendar("1970-01-02T00:00Z");
65 public GardenaThingHandler(Thing thing) {
70 public void initialize() {
72 Device device = getDevice();
73 updateProperties(device);
74 updateSettings(device);
76 } catch (GardenaException ex) {
77 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, ex.getMessage());
78 } catch (AccountHandlerNotAvailableException ex) {
84 * Updates the thing configuration from the Gardena device.
86 protected void updateSettings(Device device) throws GardenaException {
87 if (GardenaSmartImpl.DEVICE_CATEGORY_PUMP.equals(device.getCategory())) {
88 Configuration config = editConfiguration();
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);
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()));
108 * Updates the thing properties from the Gardena device.
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);
122 private void setProperty(Map<String, String> properties, Ability deviceInfo, String propertyName) {
124 properties.put(propertyName, deviceInfo.getProperty(propertyName).getValueAsString());
125 } catch (GardenaException ex) {
126 logger.debug("Ignoring missing device property {}", propertyName);
131 public void channelLinked(ChannelUID channelUID) {
133 updateChannel(channelUID);
134 } catch (GardenaDeviceNotFoundException | AccountHandlerNotAvailableException ex) {
135 logger.debug("{}", ex.getMessage(), ex);
136 } catch (GardenaException ex) {
137 logger.error("{}", ex.getMessage(), ex);
142 * Updates the channel from the Gardena device.
144 protected void updateChannel(ChannelUID channelUID) throws GardenaException, AccountHandlerNotAvailableException {
145 Device device = getDevice();
146 State state = convertToState(device, channelUID);
148 updateState(channelUID, state);
153 * Converts a Gardena property value to a openHAB state.
155 private State convertToState(Device device, ChannelUID channelUID) throws GardenaException {
156 String abilityName = channelUID.getGroupId();
157 String propertyName = channelUID.getIdWithoutGroup();
160 String value = device.getAbility(abilityName).getProperty(propertyName).getValueAsString();
162 if (StringUtils.trimToNull(value) == null || StringUtils.equals(value, "N/A")) {
163 return UnDefType.NULL;
166 switch (getThing().getChannel(channelUID.getId()).getAcceptedItemType()) {
168 return new StringType(value);
170 if (ABILITY_RADIO.equals(abilityName) && PROPERTY_STATE.equals(propertyName)) {
173 return new DecimalType(1);
175 return new DecimalType(2);
177 return new DecimalType(4);
179 return UnDefType.NULL;
182 return new DecimalType(value);
184 return Boolean.TRUE.toString().equalsIgnoreCase(value) || "on".equalsIgnoreCase(value)
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()));
192 return UnDefType.NULL;
195 } catch (GardenaException e) {
196 logger.warn("Channel '{}' cannot be updated as device does not contain property '{}:{}'", channelUID,
197 abilityName, propertyName);
203 * Converts an openHAB type to a Gardena command property.
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();
217 public void handleCommand(ChannelUID channelUID, Command command) {
219 GardenaSmartCommandName commandName = getCommandName(channelUID);
220 logger.debug("Received Gardena command: {}", commandName);
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);
227 updateChannel(channelUID);
229 } else if (commandName != null) {
230 getGardenaSmart().sendCommand(getDevice(), commandName, convertFromType(command));
232 } catch (AccountHandlerNotAvailableException | GardenaDeviceNotFoundException ex) {
234 } catch (Exception ex) {
235 logger.warn("{}", ex.getMessage(), ex);
240 * Returns the Gardena command from the channel.
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;
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;
262 return MEASURE_LIGHT;
264 case "outlet#button_manual_override_time":
265 return OUTLET_MANUAL_OVERRIDE_TIME;
266 case "outlet#valve_open":
269 case "power#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;
284 case "manual_watering#manual_watering_timer":
285 return PUMP_MANUAL_WATERING_TIMER;
293 * Updates the thing status based on the Gardena device status.
295 protected void updateStatus(Device device) {
296 String connectionStatus = "";
298 connectionStatus = device.getAbility(ABILITY_RADIO).getProperty(PROPERTY_CONNECTION_STATUS)
300 } catch (GardenaException ex) {
301 // ignore, device has no connection status property
304 boolean isUnreach = PROPERTY_CONNECTION_STATUS_UNREACH_VALUE.equals(connectionStatus);
306 ThingStatus oldStatus = thing.getStatus();
307 ThingStatus newStatus = ThingStatus.ONLINE;
308 ThingStatusDetail newDetail = ThingStatusDetail.NONE;
311 newStatus = ThingStatus.OFFLINE;
312 newDetail = ThingStatusDetail.COMMUNICATION_ERROR;
313 } else if (!device.isConfigurationSynchronized()) {
314 newStatus = thing.getStatus();
315 newDetail = ThingStatusDetail.CONFIGURATION_PENDING;
318 if (oldStatus != newStatus || thing.getStatusInfo().getStatusDetail() != newDetail) {
319 updateStatus(newStatus, newDetail);
324 public void handleConfigurationUpdate(Map<String, Object> configurationParameters)
325 throws ConfigValidationException {
326 validateConfigurationParameters(configurationParameters);
329 GardenaSmart gardena = getGardenaSmart();
330 Device device = gardena.getDevice(UidUtils.getGardenaDeviceId(getThing()));
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);
339 Setting setting = device.getSetting(key);
340 if (ObjectUtils.notEqual(setting.getValue(), newValue)) {
341 gardena.sendSetting(setting, newValue);
342 setting.setValue(newValue);
345 updateSettings(device);
346 } catch (GardenaException | AccountHandlerNotAvailableException ex) {
347 logger.warn("Error setting thing properties: {}", ex.getMessage(), ex);
352 * Returns the Gardena device for this ThingHandler.
354 private Device getDevice() throws GardenaException, AccountHandlerNotAvailableException {
355 return getGardenaSmart().getDevice(UidUtils.getGardenaDeviceId(getThing()));
359 * Returns the Gardena Smart Home implementation if the bridge is available.
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);
367 throw new AccountHandlerNotAvailableException("Gardena AccountHandler not yet available!");
370 return ((GardenaAccountHandler) getBridge().getHandler()).getGardenaSmart();