2 * Copyright (c) 2010-2023 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.smartthings.internal.converter;
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
20 import org.openhab.binding.smartthings.internal.handler.SmartthingsThingConfig;
21 import org.openhab.core.library.types.DateTimeType;
22 import org.openhab.core.library.types.DecimalType;
23 import org.openhab.core.library.types.HSBType;
24 import org.openhab.core.library.types.IncreaseDecreaseType;
25 import org.openhab.core.library.types.NextPreviousType;
26 import org.openhab.core.library.types.OnOffType;
27 import org.openhab.core.library.types.OpenClosedType;
28 import org.openhab.core.library.types.PercentType;
29 import org.openhab.core.library.types.PlayPauseType;
30 import org.openhab.core.library.types.PointType;
31 import org.openhab.core.library.types.RewindFastforwardType;
32 import org.openhab.core.library.types.StopMoveType;
33 import org.openhab.core.library.types.StringListType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.library.types.UpDownType;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.types.State;
41 import org.openhab.core.types.UnDefType;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * Base converter class.
47 * The converter classes are responsible for converting "state" messages from the smartthings hub into openHAB States.
48 * And, converting handler.handleCommand() into messages to be sent to smartthings
50 * @author Bob Raker - Initial contribution
53 public abstract class SmartthingsConverter {
55 private final Logger logger = LoggerFactory.getLogger(SmartthingsConverter.class);
57 protected String smartthingsName;
58 protected String thingTypeId;
60 SmartthingsConverter(Thing thing) {
61 smartthingsName = thing.getConfiguration().as(SmartthingsThingConfig.class).smartthingsName;
62 thingTypeId = thing.getThingTypeUID().getId();
65 public abstract String convertToSmartthings(ChannelUID channelUid, Command command);
67 public abstract State convertToOpenHab(@Nullable String acceptedChannelType,
68 SmartthingsStateData dataFromSmartthings);
71 * Provide a default converter in the base call so it can be used in sub-classes if needed
74 * @return The json string to send to Smartthings
76 protected String defaultConvertToSmartthings(ChannelUID channelUid, Command command) {
79 if (command instanceof DateTimeType dateTimeCommand) {
80 value = dateTimeCommand.format("%m/%d/%Y %H.%M.%S");
81 } else if (command instanceof HSBType hsbCommand) {
82 value = String.format("[%d, %d, %d ]", hsbCommand.getHue().intValue(),
83 hsbCommand.getSaturation().intValue(), hsbCommand.getBrightness().intValue());
84 } else if (command instanceof DecimalType) {
85 value = command.toString();
86 } else if (command instanceof IncreaseDecreaseType) { // Need to surround with double quotes
87 value = surroundWithQuotes(command.toString().toLowerCase());
88 } else if (command instanceof NextPreviousType) { // Need to surround with double quotes
89 value = surroundWithQuotes(command.toString().toLowerCase());
90 } else if (command instanceof OnOffType) { // Need to surround with double quotes
91 value = surroundWithQuotes(command.toString().toLowerCase());
92 } else if (command instanceof OpenClosedType) { // Need to surround with double quotes
93 value = surroundWithQuotes(command.toString().toLowerCase());
94 } else if (command instanceof PercentType) {
95 value = command.toString();
96 } else if (command instanceof PointType) { // There is not a comparable type in Smartthings, log and send value
98 "Warning - PointType Command is not supported by Smartthings. Please configure to use a different command type. CapabilityKey: {}, displayName: {}, capabilityAttribute {}",
99 thingTypeId, smartthingsName, channelUid.getId());
100 value = command.toFullString();
101 } else if (command instanceof RefreshType) { // Need to surround with double quotes
102 value = surroundWithQuotes(command.toString().toLowerCase());
103 } else if (command instanceof RewindFastforwardType) { // Need to surround with double quotes
104 value = surroundWithQuotes(command.toString().toLowerCase());
105 } else if (command instanceof StopMoveType) { // Need to surround with double quotes
106 value = surroundWithQuotes(command.toString().toLowerCase());
107 } else if (command instanceof PlayPauseType) { // Need to surround with double quotes
108 value = surroundWithQuotes(command.toString().toLowerCase());
109 } else if (command instanceof StringListType) {
110 value = surroundWithQuotes(command.toString());
111 } else if (command instanceof StringType) {
112 value = surroundWithQuotes(command.toString());
113 } else if (command instanceof UpDownType) { // Need to surround with double quotes
114 value = surroundWithQuotes(command.toString().toLowerCase());
117 "Warning - The Smartthings converter does not know how to handle the {} command. The Smartthingsonverter class should be updated. CapabilityKey: {}, displayName: {}, capabilityAttribute {}",
118 command.getClass().getName(), thingTypeId, smartthingsName, channelUid.getId());
119 value = command.toString().toLowerCase();
122 return String.format(
123 "{\"capabilityKey\": \"%s\", \"deviceDisplayName\": \"%s\", \"capabilityAttribute\": \"%s\", \"value\": %s}",
124 thingTypeId, smartthingsName, channelUid.getId(), value);
127 protected String surroundWithQuotes(String param) {
128 return (new StringBuilder()).append('"').append(param).append('"').toString();
131 protected State defaultConvertToOpenHab(@Nullable String acceptedChannelType,
132 SmartthingsStateData dataFromSmartthings) {
133 // If there is no stateMap the just return null State
134 if (acceptedChannelType == null) {
135 return UnDefType.NULL;
138 String deviceType = dataFromSmartthings.capabilityAttribute;
139 Object deviceValue = dataFromSmartthings.value;
141 // deviceValue can be null, handle that up front
142 if (deviceValue == null) {
143 return UnDefType.NULL;
146 switch (acceptedChannelType) {
149 "Conversion of Color is not supported by the default Smartthings to opemHAB converter. The ThingType should specify an appropriate converter. Device name: {}, Attribute: {}.",
150 dataFromSmartthings.deviceDisplayName, deviceType);
151 return UnDefType.UNDEF;
153 return "open".equals(deviceValue) ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
155 return UnDefType.UNDEF;
157 // The value coming in should be a number
158 if (deviceValue instanceof String stringCommand) {
159 return new PercentType(stringCommand);
161 logger.warn("Failed to convert {} with a value of {} from class {} to an appropriate type.",
162 deviceType, deviceValue, deviceValue.getClass().getName());
163 return UnDefType.UNDEF;
166 if (deviceValue instanceof String stringCommand2) {
167 return new DecimalType(Double.parseDouble(stringCommand2));
168 } else if (deviceValue instanceof Double) {
169 return new DecimalType((Double) deviceValue);
170 } else if (deviceValue instanceof Long) {
171 return new DecimalType((Long) deviceValue);
173 logger.warn("Failed to convert Number {} with a value of {} from class {} to an appropriate type.",
174 deviceType, deviceValue, deviceValue.getClass().getName());
175 return UnDefType.UNDEF;
178 logger.warn("Conversion of Player is not currently supported. Need to provide support for message {}.",
180 return UnDefType.UNDEF;
181 case "Rollershutter":
182 return "open".equals(deviceValue) ? UpDownType.DOWN : UpDownType.UP;
184 return new StringType((String) deviceValue);
186 return "on".equals(deviceValue) ? OnOffType.ON : OnOffType.OFF;
188 // Vector3 can't be triggered now but keep it to handle acceleration device
190 // This is a weird result from Smartthings. If the messages is from a "state" request the result will
191 // look like: "value":{"z":22,"y":-36,"x":-987}
192 // But if the result is from sensor change via a subscription to a threeAxis device the results will
193 // be a String of the format "value":"-873,-70,484"
194 // which GSON returns as a LinkedTreeMap
195 if (deviceValue instanceof String stringCommand3) {
196 return new StringType(stringCommand3);
197 } else if (deviceValue instanceof Map<?, ?>) {
198 Map<String, String> map = (Map<String, String>) deviceValue;
199 String s = String.format("%.0f,%.0f,%.0f", map.get("x"), map.get("y"), map.get("z"));
200 return new StringType(s);
203 "Unable to convert {} which should be in Smartthings Vector3 format to a string. The returned datatype from Smartthings is {}.",
204 deviceType, deviceValue.getClass().getName());
205 return UnDefType.UNDEF;
208 logger.warn("No type defined to convert {} with a value of {} from class {} to an appropriate type.",
209 deviceType, deviceValue, deviceValue.getClass().getName());
210 return UnDefType.UNDEF;