2 * Copyright (c) 2010-2021 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.somfytahoma.internal.handler;
15 import static org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants.*;
17 import java.util.HashMap;
18 import java.util.List;
21 import javax.measure.Unit;
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;
56 * The {@link SomfyTahomaBaseThingHandler} is base thing handler for all things.
58 * @author Ondrej Pecta - Initial contribution
59 * @author Laurent Garnier - Setting of channels at init + UoM for channels
62 public abstract class SomfyTahomaBaseThingHandler extends BaseThingHandler {
64 private final Logger logger = LoggerFactory.getLogger(getClass());
65 private HashMap<String, Integer> typeTable = new HashMap<>();
66 protected HashMap<String, String> stateNames = new HashMap<>();
68 protected String url = "";
70 private Map<String, Unit<?>> units = new HashMap<>();
72 public SomfyTahomaBaseThingHandler(Thing 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);
81 public HashMap<String, String> getStateNames() {
86 public void initialize() {
87 Bridge bridge = getBridge();
88 initializeThing(bridge != null ? bridge.getStatus() : null);
92 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
93 initializeThing(bridgeStatusInfo.getStatus());
96 public void initializeThing(@Nullable ThingStatus bridgeStatus) {
97 SomfyTahomaBridgeHandler bridgeHandler = getBridgeHandler();
98 if (bridgeHandler != null && bridgeStatus != null) {
100 if (getThing().getProperties().containsKey(RSSI_LEVEL_STATE)) {
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);
111 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, UNAVAILABLE);
114 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
117 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
121 private void createRSSIChannel() {
122 if (thing.getChannel(RSSI) == null) {
123 logger.debug("{} Creating a rssi channel", url);
124 createChannel(RSSI, "Number", "RSSI Level");
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());
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());
143 public Logger getLogger() {
147 protected boolean isAlwaysOnline() {
151 protected @Nullable SomfyTahomaBridgeHandler getBridgeHandler() {
152 Bridge localBridge = this.getBridge();
153 return localBridge != null ? (SomfyTahomaBridgeHandler) localBridge.getHandler() : null;
156 private String getURL() {
157 return getThing().getConfiguration().get("url") != null ? getThing().getConfiguration().get("url").toString()
161 private void setAvailable() {
162 if (ThingStatus.ONLINE != thing.getStatus()) {
163 updateStatus(ThingStatus.ONLINE);
167 private void setUnavailable() {
168 if (ThingStatus.OFFLINE != thing.getStatus() && !isAlwaysOnline()) {
169 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, UNAVAILABLE);
173 protected void sendCommand(String cmd) {
174 sendCommand(cmd, "[]");
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");
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);
192 protected void executeActionGroup() {
193 SomfyTahomaBridgeHandler handler = getBridgeHandler();
194 if (handler != null) {
195 handler.executeActionGroup(url);
199 protected @Nullable String getCurrentExecutions() {
200 SomfyTahomaBridgeHandler handler = getBridgeHandler();
201 if (handler != null) {
202 return handler.getCurrentExecutions(url);
207 protected void cancelExecution(String executionId) {
208 SomfyTahomaBridgeHandler handler = getBridgeHandler();
209 if (handler != null) {
210 handler.cancelExecution(executionId);
214 protected SomfyTahomaStatus getTahomaStatus(String id) {
215 SomfyTahomaBridgeHandler handler = getBridgeHandler();
216 if (handler != null) {
217 return handler.getTahomaStatus(id);
219 return new SomfyTahomaStatus();
222 private void cacheStateType(SomfyTahomaState state) {
223 if (state.getType() > 0 && !typeTable.containsKey(state.getName())) {
224 typeTable.put(state.getName(), state.getType());
228 protected void cacheStateType(String stateName, int type) {
229 if (type > 0 && !typeTable.containsKey(stateName)) {
230 typeTable.put(stateName, type);
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);
242 case "core:TemperatureInKelvin":
243 units.put("Number:Temperature", Units.KELVIN);
245 case "core:TemperatureInFahrenheit":
246 units.put("Number:Temperature", ImperialUnits.FAHRENHEIT);
248 case "core:RelativeValueInPercentage":
249 units.put("Number:Dimensionless", Units.PERCENT);
251 case "core:LuminanceInLux":
252 units.put("Number:Illuminance", Units.LUX);
254 case "core:ElectricalEnergyInWh":
255 units.put("Number:Energy", Units.WATT_HOUR);
257 case "core:ElectricalEnergyInKWh":
258 units.put("Number:Energy", Units.KILOWATT_HOUR);
260 case "core:ElectricalEnergyInMWh":
261 units.put("Number:Energy", Units.MEGAWATT_HOUR);
264 logger.warn("Unhandled value \"{}\" for attribute \"core:MeasuredValueType\"", attr.getValue());
272 protected @Nullable State parseTahomaState(@Nullable SomfyTahomaState state) {
273 return parseTahomaState(null, state);
276 protected @Nullable State parseTahomaState(@Nullable String acceptedItemType, @Nullable SomfyTahomaState state) {
278 return UnDefType.NULL;
281 int type = state.getType();
284 if (typeTable.containsKey(state.getName())) {
285 type = typeTable.get(state.getName());
287 cacheStateType(state);
291 logger.debug("{} Cannot recognize the state type for: {}!", url, state.getValue());
295 logger.trace("Value to parse: {}, type: {}", state.getValue(), type);
298 Double valPct = Double.parseDouble(state.getValue().toString());
299 if (acceptedItemType != null && acceptedItemType.startsWith(CoreItemFactory.NUMBER + ":")) {
300 Unit<?> unit = units.get(acceptedItemType);
302 return new QuantityType<>(normalizePercent(valPct), unit);
304 logger.warn("Do not return a quantity for {} because the unit is unknown",
308 return new PercentType(normalizePercent(valPct));
310 Double valDec = Double.parseDouble(state.getValue().toString());
311 if (acceptedItemType != null && acceptedItemType.startsWith(CoreItemFactory.NUMBER + ":")) {
312 Unit<?> unit = units.get(acceptedItemType);
314 return new QuantityType<>(valDec, unit);
316 logger.warn("Do not return a quantity for {} because the unit is unknown",
320 return new DecimalType(valDec);
323 String value = state.getValue().toString();
324 if ("String".equals(acceptedItemType)) {
325 return new StringType(value);
327 return parseStringState(value);
332 } catch (IllegalArgumentException ex) {
333 logger.debug("{} Error while parsing Tahoma state! Value: {} type: {}", url, state.getValue(), type, ex);
338 private int normalizePercent(Double valPct) {
339 int value = valPct.intValue();
342 } else if (value > 100) {
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);
356 switch (value.toLowerCase()) {
364 return OnOffType.OFF;
366 case "nopersoninside":
369 return OpenClosedType.CLOSED;
375 return OpenClosedType.OPEN;
377 return UnDefType.UNDEF;
379 logger.debug("{} Unknown thing state returned: {}", url, value);
380 return UnDefType.UNDEF;
384 public void updateThingStatus(List<SomfyTahomaState> states) {
385 SomfyTahomaState state = getStatusState(states);
386 updateThingStatus(state);
389 private @Nullable SomfyTahomaState getStatusState(List<SomfyTahomaState> states) {
390 for (SomfyTahomaState state : states) {
391 if (STATUS_STATE.equals(state.getName()) && state.getType() == TYPE_STRING) {
398 private void updateThingStatus(@Nullable SomfyTahomaState state) {
400 // Most probably we are dealing with RTS device which does not return states
401 // so we have to setup ONLINE status manually
405 if (STATUS_STATE.equals(state.getName()) && state.getType() == TYPE_STRING) {
406 if (UNAVAILABLE.equals(state.getValue())) {
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);
423 updateThingChannels(state);
426 updateProperties(properties);
429 private void updateRSSIChannel(SomfyTahomaState state) {
431 Channel ch = thing.getChannel(RSSI);
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);
441 public void updateThingChannels(SomfyTahomaState state) {
442 stateNames.forEach((k, v) -> {
443 if (v.equals(state.getName())) {
444 Channel ch = thing.getChannel(k);
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);
456 public int toInteger(Command command) {
457 return (command instanceof DecimalType) ? ((DecimalType) command).intValue() : 0;