]> git.basschouten.com Git - openhab-addons.git/blob
ab9044904b8d0c89e9f5ce73172a1b6dab6012c3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.smartthings.internal.handler;
14
15 import java.lang.reflect.Constructor;
16 import java.lang.reflect.InvocationTargetException;
17 import java.util.Collection;
18 import java.util.HashMap;
19 import java.util.LinkedList;
20 import java.util.Map;
21 import java.util.concurrent.ExecutionException;
22 import java.util.concurrent.TimeoutException;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.smartthings.internal.SmartthingsBindingConstants;
27 import org.openhab.binding.smartthings.internal.SmartthingsHandlerFactory;
28 import org.openhab.binding.smartthings.internal.converter.SmartthingsConverter;
29 import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
30 import org.openhab.core.config.core.status.ConfigStatusMessage;
31 import org.openhab.core.thing.Bridge;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.ConfigStatusThingHandler;
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;
44
45 /**
46  * @author Bob Raker - Initial contribution
47  */
48 @NonNullByDefault
49 public class SmartthingsThingHandler extends ConfigStatusThingHandler {
50
51     private final Logger logger = LoggerFactory.getLogger(SmartthingsThingHandler.class);
52
53     private SmartthingsThingConfig config;
54     private String smartthingsName;
55     private int timeout;
56     private SmartthingsHandlerFactory smartthingsHandlerFactory;
57     private Map<ChannelUID, SmartthingsConverter> converters = new HashMap<ChannelUID, SmartthingsConverter>();
58
59     private final String smartthingsConverterName = "smartthings-converter";
60
61     public SmartthingsThingHandler(Thing thing, SmartthingsHandlerFactory smartthingsHandlerFactory) {
62         super(thing);
63         this.smartthingsHandlerFactory = smartthingsHandlerFactory;
64         smartthingsName = ""; // Initialize here so it can be NonNull but it should always get a value in initialize()
65         config = new SmartthingsThingConfig();
66     }
67
68     /**
69      * Called when openHAB receives a command for this handler
70      *
71      * @param channelUID The channel the command was sent to
72      * @param command The command sent
73      */
74     @Override
75     public void handleCommand(ChannelUID channelUID, Command command) {
76         Bridge bridge = getBridge();
77
78         // Check if the bridge has not been initialized yet
79         if (bridge == null) {
80             logger.debug(
81                     "The bridge has not been initialized yet. Can not process command for channel {} with command {}.",
82                     channelUID.getAsString(), command.toFullString());
83             return;
84         }
85
86         SmartthingsBridgeHandler smartthingsBridgeHandler = (SmartthingsBridgeHandler) bridge.getHandler();
87         if (smartthingsBridgeHandler != null
88                 && smartthingsBridgeHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
89             String thingTypeId = thing.getThingTypeUID().getId();
90             String smartthingsType = getSmartthingsAttributeFromChannel(channelUID);
91
92             SmartthingsConverter converter = converters.get(channelUID);
93
94             String path;
95             String jsonMsg;
96             if (command instanceof RefreshType) {
97                 path = "/state";
98                 // Go to ST hub and ask for current state
99                 jsonMsg = String.format(
100                         "{\"capabilityKey\": \"%s\", \"deviceDisplayName\": \"%s\", \"capabilityAttribute\": \"%s\", \"openHabStartTime\": %d}",
101                         thingTypeId, smartthingsName, smartthingsType, System.currentTimeMillis());
102             } else {
103                 // Send update to ST hub
104                 path = "/update";
105                 jsonMsg = converter.convertToSmartthings(channelUID, command);
106
107                 // The smartthings hub won't (can't) return a response to this call. But, it will send a separate
108                 // message back to the SmartthingBridgeHandler.receivedPushMessage handler
109             }
110
111             try {
112                 smartthingsHandlerFactory.sendDeviceCommand(path, timeout, jsonMsg);
113                 // Smartthings will not return a response to this message but will send it's response message
114                 // which will get picked up by the SmartthingBridgeHandler.receivedPushMessage handler
115             } catch (InterruptedException | TimeoutException | ExecutionException e) {
116                 logger.warn("Attempt to send command to the Smartthings hub for {} failed with exception: {}",
117                         smartthingsName, e.getMessage());
118             }
119         }
120     }
121
122     /**
123      * Get the Smartthings capability reference "attribute" from the channel properties.
124      * In OpenHAB each channel id corresponds to the Smartthings attribute. In the ChannelUID the
125      * channel id is the last segment
126      *
127      * @param channelUID
128      * @return channel id
129      */
130     private String getSmartthingsAttributeFromChannel(ChannelUID channelUID) {
131         return channelUID.getId();
132     }
133
134     /**
135      * State messages sent from the hub arrive here, are processed and the openHab state is updated.
136      *
137      * @param stateData
138      */
139     public void handleStateMessage(SmartthingsStateData stateData) {
140         // First locate the channel
141         Channel matchingChannel = null;
142         for (Channel ch : thing.getChannels()) {
143             if (ch.getUID().getAsString().endsWith(stateData.capabilityAttribute)) {
144                 matchingChannel = ch;
145                 break;
146             }
147         }
148         if (matchingChannel == null) {
149             return;
150         }
151
152         SmartthingsConverter converter = converters.get(matchingChannel.getUID());
153
154         // If value from Smartthings is null then stop here
155         State state;
156         if (stateData.value != null) {
157             state = converter.convertToOpenHab(matchingChannel.getAcceptedItemType(), stateData);
158         } else {
159             state = UnDefType.NULL;
160         }
161
162         updateState(matchingChannel.getUID(), state);
163         logger.trace("Smartthings updated State for channel: {} to {}", matchingChannel.getUID().getAsString(),
164                 state.toString());
165     }
166
167     @Override
168     public void initialize() {
169         config = getThing().getConfiguration().as(SmartthingsThingConfig.class);
170         if (!validateConfig(config)) {
171             return;
172         }
173         smartthingsName = config.smartthingsName;
174         timeout = config.smartthingsTimeout;
175
176         // Create converters for each channel
177         for (Channel ch : thing.getChannels()) {
178             @Nullable
179             String converterName = ch.getProperties().get(smartthingsConverterName);
180             // Will be null if no explicit converter was specified
181             if (converterName == null || converterName.isEmpty()) {
182                 // A converter was Not specified so use the channel id
183                 converterName = ch.getUID().getId();
184             }
185
186             // Try to get the converter
187             SmartthingsConverter cvtr = getConverter(converterName);
188             if (cvtr == null) {
189                 // If there is no channel specific converter the get the "default" converter
190                 cvtr = getConverter("default");
191             }
192
193             if (cvtr != null) {
194                 // cvtr should never be null because there should always be a "default" converter
195                 converters.put(ch.getUID(), cvtr);
196             }
197         }
198
199         updateStatus(ThingStatus.ONLINE);
200     }
201
202     private @Nullable SmartthingsConverter getConverter(String converterName) {
203         // Converter name will be a name such as "switch" which has to be converted into the full class name such as
204         // org.openhab.binding.smartthings.internal.converter.SmartthingsSwitchConveter
205         StringBuffer converterClassName = new StringBuffer(
206                 "org.openhab.binding.smartthings.internal.converter.Smartthings");
207         converterClassName.append(Character.toUpperCase(converterName.charAt(0)));
208         converterClassName.append(converterName.substring(1));
209         converterClassName.append("Converter");
210         try {
211             Constructor<?> constr = Class.forName(converterClassName.toString()).getDeclaredConstructor(Thing.class);
212             constr.setAccessible(true);
213             return (SmartthingsConverter) constr.newInstance(thing);
214         } catch (ClassNotFoundException e) {
215             // Most of the time there is no channel specific converter, the default converter is all that is needed.
216             logger.trace("No Custom converter exists for {} ({})", converterName, converterClassName);
217         } catch (NoSuchMethodException e) {
218             logger.warn("NoSuchMethodException occurred for {} ({}) {}", converterName, converterClassName,
219                     e.getMessage());
220         } catch (InvocationTargetException e) {
221             logger.warn("InvocationTargetException occurred for {} ({}) {}", converterName, converterClassName,
222                     e.getMessage());
223         } catch (IllegalAccessException e) {
224             logger.warn("IllegalAccessException occurred for {} ({}) {}", converterName, converterClassName,
225                     e.getMessage());
226         } catch (InstantiationException e) {
227             logger.warn("InstantiationException occurred for {} ({}) {}", converterName, converterClassName,
228                     e.getMessage());
229         }
230         return null;
231     }
232
233     private boolean validateConfig(SmartthingsThingConfig config) {
234         String name = config.smartthingsName;
235         if (name.isEmpty()) {
236             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
237                     "Smartthings device name is missing");
238             return false;
239         }
240
241         return true;
242     }
243
244     @Override
245     public Collection<ConfigStatusMessage> getConfigStatus() {
246         Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<ConfigStatusMessage>();
247
248         // The name must be provided
249         String stName = config.smartthingsName;
250         if (stName.isEmpty()) {
251             configStatusMessages.add(ConfigStatusMessage.Builder.error(SmartthingsBindingConstants.SMARTTHINGS_NAME)
252                     .withMessageKeySuffix(SmartthingsThingConfigStatusMessage.SMARTTHINGS_NAME_MISSING)
253                     .withArguments(SmartthingsBindingConstants.SMARTTHINGS_NAME).build());
254         }
255
256         return configStatusMessages;
257     }
258
259     public String getSmartthingsName() {
260         return smartthingsName;
261     }
262
263     @Override
264     public String toString() {
265         StringBuffer sb = new StringBuffer();
266         sb.append("smartthingsName :").append(smartthingsName);
267         sb.append(", thing UID: ").append(this.thing.getUID());
268         sb.append(", thing label: ").append(this.thing.getLabel());
269         return sb.toString();
270     }
271 }