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