]> git.basschouten.com Git - openhab-addons.git/blob
5a14b35180bfa96c69b7a5720f788d16bb15f8a0
[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.somfytahoma.internal.handler;
14
15 import static org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Objects;
22
23 import javax.measure.Unit;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaDevice;
28 import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaState;
29 import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaStatus;
30 import org.openhab.core.library.CoreItemFactory;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.OnOffType;
33 import org.openhab.core.library.types.OpenClosedType;
34 import org.openhab.core.library.types.PercentType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.types.StringType;
37 import org.openhab.core.library.unit.ImperialUnits;
38 import org.openhab.core.library.unit.SIUnits;
39 import org.openhab.core.library.unit.Units;
40 import org.openhab.core.thing.Bridge;
41 import org.openhab.core.thing.Channel;
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.ThingStatusInfo;
47 import org.openhab.core.thing.binding.BaseThingHandler;
48 import org.openhab.core.thing.binding.builder.ChannelBuilder;
49 import org.openhab.core.thing.binding.builder.ThingBuilder;
50 import org.openhab.core.types.Command;
51 import org.openhab.core.types.RefreshType;
52 import org.openhab.core.types.State;
53 import org.openhab.core.types.UnDefType;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 /**
58  * The {@link SomfyTahomaBaseThingHandler} is base thing handler for all things.
59  *
60  * @author Ondrej Pecta - Initial contribution
61  * @author Laurent Garnier - Setting of channels at init + UoM for channels
62  */
63 @NonNullByDefault
64 public abstract class SomfyTahomaBaseThingHandler extends BaseThingHandler {
65
66     private final Logger logger = LoggerFactory.getLogger(getClass());
67     private HashMap<String, Integer> typeTable = new HashMap<>();
68     protected HashMap<String, String> stateNames = new HashMap<>();
69
70     protected String url = "";
71
72     private Map<String, Unit<?>> units = new HashMap<>();
73
74     public SomfyTahomaBaseThingHandler(Thing thing) {
75         super(thing);
76         // Define default units
77         units.put("Number:Temperature", SIUnits.CELSIUS);
78         units.put("Number:Energy", Units.WATT_HOUR);
79         units.put("Number:Illuminance", Units.LUX);
80         units.put("Number:Dimensionless", Units.PERCENT);
81     }
82
83     public HashMap<String, String> getStateNames() {
84         return stateNames;
85     }
86
87     @Override
88     public void initialize() {
89         Bridge bridge = getBridge();
90         initializeThing(bridge != null ? bridge.getStatus() : null);
91     }
92
93     @Override
94     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
95         initializeThing(bridgeStatusInfo.getStatus());
96     }
97
98     public void initializeThing(@Nullable ThingStatus bridgeStatus) {
99         SomfyTahomaBridgeHandler bridgeHandler = getBridgeHandler();
100         if (bridgeHandler != null && bridgeStatus != null) {
101             url = getURL();
102             if (getThing().getProperties().containsKey(RSSI_LEVEL_STATE)) {
103                 createRSSIChannel();
104             }
105             if (bridgeStatus == ThingStatus.ONLINE) {
106                 SomfyTahomaDevice device = bridgeHandler.getCachedDevice(url);
107                 if (device != null) {
108                     updateUnits(device.getAttributes());
109                     List<SomfyTahomaState> states = device.getStates();
110                     updateThingStatus(states);
111                     updateThingChannels(states);
112                 } else {
113                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, UNAVAILABLE);
114                 }
115             } else {
116                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
117             }
118         } else {
119             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
120         }
121     }
122
123     private void createRSSIChannel() {
124         if (thing.getChannel(RSSI) == null) {
125             logger.debug("{} Creating a rssi channel", url);
126             createChannel(RSSI, "Number", "RSSI Level");
127         }
128     }
129
130     private void createChannel(String name, String type, String label) {
131         ThingBuilder thingBuilder = editThing();
132         Channel channel = ChannelBuilder.create(new ChannelUID(thing.getUID(), name), type).withLabel(label).build();
133         thingBuilder.withChannel(channel);
134         updateThing(thingBuilder.build());
135     }
136
137     @Override
138     public void handleCommand(ChannelUID channelUID, Command command) {
139         logger.debug("{} Received command {} for channel {}", url, command, channelUID);
140         if (command instanceof RefreshType) {
141             refresh(channelUID.getId());
142         }
143     }
144
145     public Logger getLogger() {
146         return logger;
147     }
148
149     protected @Nullable SomfyTahomaBridgeHandler getBridgeHandler() {
150         Bridge localBridge = this.getBridge();
151         return localBridge != null ? (SomfyTahomaBridgeHandler) localBridge.getHandler() : null;
152     }
153
154     private String getURL() {
155         return getThing().getConfiguration().get("url") != null ? getThing().getConfiguration().get("url").toString()
156                 : "";
157     }
158
159     private void setAvailable() {
160         if (ThingStatus.ONLINE != thing.getStatus()) {
161             updateStatus(ThingStatus.ONLINE);
162         }
163     }
164
165     private void setUnavailable() {
166         if (ThingStatus.OFFLINE != thing.getStatus()) {
167             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, UNAVAILABLE);
168         }
169     }
170
171     protected void sendCommand(String cmd) {
172         sendCommand(cmd, "[]");
173     }
174
175     protected void sendCommand(String cmd, String param) {
176         SomfyTahomaBridgeHandler handler = getBridgeHandler();
177         if (handler != null) {
178             handler.sendCommand(url, cmd, param, EXEC_URL + "apply");
179         }
180     }
181
182     protected void sendCommandToSameDevicesInPlace(String cmd) {
183         sendCommandToSameDevicesInPlace(cmd, "[]");
184     }
185
186     protected void sendCommandToSameDevicesInPlace(String cmd, String param) {
187         SomfyTahomaBridgeHandler handler = getBridgeHandler();
188         if (handler != null) {
189             handler.sendCommandToSameDevicesInPlace(url, cmd, param, EXEC_URL + "apply");
190         }
191     }
192
193     protected void refresh(String channel) {
194         SomfyTahomaBridgeHandler handler = getBridgeHandler();
195         String stateName = stateNames.get(channel);
196         if (handler != null && stateName != null) {
197             handler.refresh(url, stateName);
198         }
199     }
200
201     protected void executeActionGroup() {
202         SomfyTahomaBridgeHandler handler = getBridgeHandler();
203         if (handler != null) {
204             handler.executeActionGroup(url);
205         }
206     }
207
208     protected @Nullable String getCurrentExecutions() {
209         SomfyTahomaBridgeHandler handler = getBridgeHandler();
210         if (handler != null) {
211             return handler.getCurrentExecutions(url);
212         }
213         return null;
214     }
215
216     protected void cancelExecution(String executionId) {
217         SomfyTahomaBridgeHandler handler = getBridgeHandler();
218         if (handler != null) {
219             handler.cancelExecution(executionId);
220         }
221     }
222
223     protected SomfyTahomaStatus getTahomaStatus(String id) {
224         SomfyTahomaBridgeHandler handler = getBridgeHandler();
225         if (handler != null) {
226             return handler.getTahomaStatus(id);
227         }
228         return new SomfyTahomaStatus();
229     }
230
231     private void cacheStateType(SomfyTahomaState state) {
232         if (state.getType() > 0 && !typeTable.containsKey(state.getName())) {
233             typeTable.put(state.getName(), state.getType());
234         }
235     }
236
237     protected void cacheStateType(String stateName, int type) {
238         if (type > 0 && !typeTable.containsKey(stateName)) {
239             typeTable.put(stateName, type);
240         }
241     }
242
243     protected Unit<?> getTemperatureUnit() {
244         return Objects.requireNonNull(units.get("Number:Temperature"));
245     }
246
247     private void updateUnits(List<SomfyTahomaState> attributes) {
248         for (SomfyTahomaState attr : attributes) {
249             if ("core:MeasuredValueType".equals(attr.getName()) && attr.getType() == TYPE_STRING) {
250                 switch ((String) attr.getValue()) {
251                     case "core:TemperatureInCelcius":
252                     case "core:TemperatureInCelsius":
253                         units.put("Number:Temperature", SIUnits.CELSIUS);
254                         break;
255                     case "core:TemperatureInKelvin":
256                         units.put("Number:Temperature", Units.KELVIN);
257                         break;
258                     case "core:TemperatureInFahrenheit":
259                         units.put("Number:Temperature", ImperialUnits.FAHRENHEIT);
260                         break;
261                     case "core:RelativeValueInPercentage":
262                         units.put("Number:Dimensionless", Units.PERCENT);
263                         break;
264                     case "core:LuminanceInLux":
265                         units.put("Number:Illuminance", Units.LUX);
266                         break;
267                     case "core:ElectricalEnergyInWh":
268                         units.put("Number:Energy", Units.WATT_HOUR);
269                         break;
270                     case "core:ElectricalEnergyInKWh":
271                         units.put("Number:Energy", Units.KILOWATT_HOUR);
272                         break;
273                     case "core:ElectricalEnergyInMWh":
274                         units.put("Number:Energy", Units.MEGAWATT_HOUR);
275                         break;
276                     default:
277                         logger.warn("Unhandled value \"{}\" for attribute \"core:MeasuredValueType\"", attr.getValue());
278                         break;
279                 }
280                 break;
281             }
282         }
283     }
284
285     protected @Nullable State parseTahomaState(@Nullable SomfyTahomaState state) {
286         return parseTahomaState(null, state);
287     }
288
289     protected @Nullable State parseTahomaState(@Nullable String acceptedItemType, @Nullable SomfyTahomaState state) {
290         if (state == null) {
291             return UnDefType.NULL;
292         }
293
294         int type = state.getType();
295
296         try {
297             if (typeTable.containsKey(state.getName())) {
298                 type = typeTable.get(state.getName());
299             } else {
300                 cacheStateType(state);
301             }
302
303             if (type == 0) {
304                 logger.debug("{} Cannot recognize the state type for: {}!", url, state.getValue());
305                 return null;
306             }
307
308             logger.trace("Value to parse: {}, type: {}", state.getValue(), type);
309             switch (type) {
310                 case TYPE_PERCENT:
311                     Double valPct = Double.parseDouble(state.getValue().toString());
312                     if (acceptedItemType != null && acceptedItemType.startsWith(CoreItemFactory.NUMBER + ":")) {
313                         Unit<?> unit = units.get(acceptedItemType);
314                         if (unit != null) {
315                             return new QuantityType<>(normalizePercent(valPct), unit);
316                         } else {
317                             logger.warn("Do not return a quantity for {} because the unit is unknown",
318                                     acceptedItemType);
319                         }
320                     }
321                     return new PercentType(normalizePercent(valPct));
322                 case TYPE_DECIMAL:
323                     Double valDec = Double.parseDouble(state.getValue().toString());
324                     if (acceptedItemType != null && acceptedItemType.startsWith(CoreItemFactory.NUMBER + ":")) {
325                         Unit<?> unit = units.get(acceptedItemType);
326                         if (unit != null) {
327                             return new QuantityType<>(valDec, unit);
328                         } else {
329                             logger.warn("Do not return a quantity for {} because the unit is unknown",
330                                     acceptedItemType);
331                         }
332                     }
333                     return new DecimalType(valDec);
334                 case TYPE_STRING:
335                 case TYPE_BOOLEAN:
336                     String value = state.getValue().toString();
337                     if ("String".equals(acceptedItemType)) {
338                         return new StringType(value);
339                     } else {
340                         return parseStringState(value);
341                     }
342                 default:
343                     return null;
344             }
345         } catch (IllegalArgumentException ex) {
346             logger.debug("{} Error while parsing Tahoma state! Value: {} type: {}", url, state.getValue(), type, ex);
347         }
348         return null;
349     }
350
351     private int normalizePercent(Double valPct) {
352         int value = valPct.intValue();
353         if (value < 0) {
354             value = 0;
355         } else if (value > 100) {
356             value = 100;
357         }
358         return value;
359     }
360
361     private State parseStringState(String value) {
362         if (value.endsWith("%")) {
363             // convert "100%" to 100 decimal
364             String val = value.replace("%", "");
365             logger.trace("converting: {} to value: {}", value, val);
366             Double valDec = Double.parseDouble(val);
367             return new DecimalType(valDec);
368         }
369         switch (value.toLowerCase()) {
370             case "on":
371             case "true":
372             case "active":
373                 return OnOffType.ON;
374             case "off":
375             case "false":
376             case "inactive":
377                 return OnOffType.OFF;
378             case "notdetected":
379             case "nopersoninside":
380             case "closed":
381             case "locked":
382                 return OpenClosedType.CLOSED;
383             case "detected":
384             case "personinside":
385             case "open":
386             case "opened":
387             case "unlocked":
388                 return OpenClosedType.OPEN;
389             case "unknown":
390                 return UnDefType.UNDEF;
391             default:
392                 logger.debug("{} Unknown thing state returned: {}", url, value);
393                 return UnDefType.UNDEF;
394         }
395     }
396
397     public void updateThingStatus(List<SomfyTahomaState> states) {
398         SomfyTahomaState state = getStatusState(states);
399         updateThingStatus(state);
400     }
401
402     private @Nullable SomfyTahomaState getStatusState(List<SomfyTahomaState> states) {
403         return getState(states, STATUS_STATE, TYPE_STRING);
404     }
405
406     private void updateThingStatus(@Nullable SomfyTahomaState state) {
407         if (state == null) {
408             // Most probably we are dealing with RTS device which does not return states
409             // so we have to setup ONLINE status manually
410             setAvailable();
411             return;
412         }
413         if (STATUS_STATE.equals(state.getName()) && state.getType() == TYPE_STRING) {
414             if (UNAVAILABLE.equals(state.getValue())) {
415                 setUnavailable();
416             } else {
417                 setAvailable();
418             }
419         }
420     }
421
422     public void updateThingChannels(List<SomfyTahomaState> states) {
423         Map<String, String> properties = new HashMap<>();
424         for (SomfyTahomaState state : states) {
425             logger.trace("{} processing state: {} with value: {}", url, state.getName(), state.getValue());
426             properties.put(state.getName(), state.getValue().toString());
427             if (RSSI_LEVEL_STATE.equals(state.getName())) {
428                 // RSSI channel is a dynamic one
429                 updateRSSIChannel(state);
430             } else {
431                 updateThingChannels(state);
432             }
433         }
434         updateProperties(properties);
435     }
436
437     private void updateRSSIChannel(SomfyTahomaState state) {
438         createRSSIChannel();
439         Channel ch = thing.getChannel(RSSI);
440         if (ch != null) {
441             logger.debug("{} updating RSSI channel with value: {}", url, state.getValue());
442             State newState = parseTahomaState(ch.getAcceptedItemType(), state);
443             if (newState != null) {
444                 updateState(ch.getUID(), newState);
445             }
446         }
447     }
448
449     public void updateThingChannels(SomfyTahomaState state) {
450         stateNames.forEach((k, v) -> {
451             if (v.equals(state.getName())) {
452                 Channel ch = thing.getChannel(k);
453                 if (ch != null) {
454                     logger.debug("{} updating channel: {} with value: {}", url, k, state.getValue());
455                     State newState = parseTahomaState(ch.getAcceptedItemType(), state);
456                     if (newState != null) {
457                         updateState(ch.getUID(), newState);
458                     }
459                 }
460             }
461         });
462     }
463
464     public int toInteger(Command command) {
465         return (command instanceof DecimalType) ? ((DecimalType) command).intValue() : 0;
466     }
467
468     public @Nullable BigDecimal toTemperature(Command command) {
469         BigDecimal temperature = null;
470         if (command instanceof QuantityType<?>) {
471             QuantityType<?> quantity = (QuantityType<?>) command;
472             QuantityType<?> convertedQuantity = quantity.toUnit(getTemperatureUnit());
473             if (convertedQuantity != null) {
474                 quantity = convertedQuantity;
475             }
476             temperature = quantity.toBigDecimal();
477         } else if (command instanceof DecimalType) {
478             temperature = ((DecimalType) command).toBigDecimal();
479         }
480         return temperature;
481     }
482
483     public static @Nullable SomfyTahomaState getState(List<SomfyTahomaState> states, String stateName,
484             @Nullable Integer stateType) {
485         for (SomfyTahomaState state : states) {
486             if (stateName.equals(state.getName()) && (stateType == null || stateType == state.getType())) {
487                 return state;
488             }
489         }
490         return null;
491     }
492 }