]> git.basschouten.com Git - openhab-addons.git/blob
6091b11d3f68cf7bd4b667aebb563f1a3ad49fdc
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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;
14
15 import static org.openhab.binding.smartthings.internal.SmartthingsBindingConstants.*;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.eclipse.jetty.client.api.ContentResponse;
28 import org.eclipse.jetty.client.util.StringContentProvider;
29 import org.eclipse.jetty.http.HttpMethod;
30 import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
31 import org.openhab.binding.smartthings.internal.handler.SmartthingsBridgeHandler;
32 import org.openhab.binding.smartthings.internal.handler.SmartthingsThingHandler;
33 import org.openhab.core.io.net.http.HttpClientFactory;
34 import org.openhab.core.thing.Bridge;
35 import org.openhab.core.thing.Channel;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.thing.ThingUID;
39 import org.openhab.core.thing.binding.BaseThingHandlerFactory;
40 import org.openhab.core.thing.binding.ThingHandler;
41 import org.openhab.core.thing.binding.ThingHandlerFactory;
42 import org.osgi.service.component.annotations.Component;
43 import org.osgi.service.component.annotations.Reference;
44 import org.osgi.service.event.Event;
45 import org.osgi.service.event.EventHandler;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import com.google.gson.Gson;
50
51 /**
52  * The {@link SmartthingsHandlerFactory} is responsible for creating things and thing
53  * handlers.
54  *
55  * @author Bob Raker - Initial contribution
56  */
57 @NonNullByDefault
58 @Component(service = { ThingHandlerFactory.class, SmartthingsHubCommand.class,
59         EventHandler.class }, configurationPid = "binding.smarthings", property = "event.topics=org/openhab/binding/smartthings/state")
60 public class SmartthingsHandlerFactory extends BaseThingHandlerFactory
61         implements ThingHandlerFactory, EventHandler, SmartthingsHubCommand {
62
63     private final Logger logger = LoggerFactory.getLogger(SmartthingsHandlerFactory.class);
64
65     private @Nullable SmartthingsBridgeHandler bridgeHandler = null;
66     private @Nullable ThingUID bridgeUID;
67     private Gson gson;
68     private List<SmartthingsThingHandler> thingHandlers = Collections.synchronizedList(new ArrayList<>());
69
70     private @NonNullByDefault({}) HttpClient httpClient;
71
72     @Override
73     public boolean supportsThingType(ThingTypeUID thingTypeUID) {
74         return THING_TYPE_SMARTTHINGS.equals(thingTypeUID) || SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
75     }
76
77     public SmartthingsHandlerFactory() {
78         // Get a Gson instance
79         gson = new Gson();
80     }
81
82     @Override
83     protected @Nullable ThingHandler createHandler(Thing thing) {
84         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
85
86         if (thingTypeUID.equals(THING_TYPE_SMARTTHINGS)) {
87             // This binding only supports one bridge. If the user tries to add a second bridge register and error and
88             // ignore
89             if (bridgeHandler != null) {
90                 logger.warn(
91                         "The Smartthings binding only supports one bridge. Please change your configuration to only use one Bridge. This bridge {} will be ignored.",
92                         thing.getUID().getAsString());
93                 return null;
94             }
95             bridgeHandler = new SmartthingsBridgeHandler((Bridge) thing, this, bundleContext);
96             bridgeUID = thing.getUID();
97             logger.debug("SmartthingsHandlerFactory created BridgeHandler for {}", thingTypeUID.getAsString());
98             return bridgeHandler;
99         } else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
100             // Everything but the bridge is handled by this one handler
101             // Make sure this thing belongs to the registered Bridge
102             if (bridgeUID != null && !bridgeUID.equals(thing.getBridgeUID())) {
103                 logger.warn("Thing: {} is being ignored because it does not belong to the registered bridge.",
104                         thing.getLabel());
105                 return null;
106             }
107             SmartthingsThingHandler thingHandler = new SmartthingsThingHandler(thing, this);
108             thingHandlers.add(thingHandler);
109             logger.debug("SmartthingsHandlerFactory created ThingHandler for {}, {}",
110                     thing.getConfiguration().get("smartthingsName"), thing.getUID().getAsString());
111             return thingHandler;
112         }
113         return null;
114     }
115
116     /**
117      * Send a command to the Smartthings Hub
118      *
119      * @param path http path which tells Smartthings what to execute
120      * @param data data to send
121      * @return Response from Smartthings
122      * @throws InterruptedException
123      * @throws TimeoutException
124      * @throws ExecutionException
125      */
126     public void sendDeviceCommand(String path, int timeout, String data)
127             throws InterruptedException, TimeoutException, ExecutionException {
128         ContentResponse response = httpClient
129                 .newRequest(bridgeHandler.getSmartthingsIp(), bridgeHandler.getSmartthingsPort())
130                 .timeout(timeout, TimeUnit.SECONDS).path(path).method(HttpMethod.POST)
131                 .content(new StringContentProvider(data), "application/json").send();
132
133         int status = response.getStatus();
134         if (status == 202) {
135             logger.debug(
136                     "Sent message \"{}\" with path \"{}\" to the Smartthings hub, received HTTP status {} (This is the normal code from Smartthings)",
137                     data, path, status);
138         } else {
139             logger.warn("Sent message \"{}\" with path \"{}\" to the Smartthings hub, received HTTP status {}", data,
140                     path, status);
141         }
142     }
143
144     /**
145      * Messages sent to the Smartthings binding from the hub via the SmartthingsServlet arrive here and are then
146      * dispatched to the correct thing's handleStateMessage function
147      *
148      * @param event The event sent
149      */
150     @Override
151     public synchronized void handleEvent(@Nullable Event event) {
152         if (event != null) {
153             String data = (String) event.getProperty("data");
154             SmartthingsStateData stateData = new SmartthingsStateData();
155             stateData = gson.fromJson(data, stateData.getClass());
156             if (stateData == null) {
157                 return;
158             }
159             SmartthingsThingHandler handler = findHandler(stateData);
160             if (handler != null) {
161                 handler.handleStateMessage(stateData);
162             }
163         }
164     }
165
166     private @Nullable SmartthingsThingHandler findHandler(SmartthingsStateData stateData) {
167         synchronized (thingHandlers) {
168             for (SmartthingsThingHandler handler : thingHandlers) {
169                 if (handler.getSmartthingsName().equals(stateData.deviceDisplayName)) {
170                     for (Channel ch : handler.getThing().getChannels()) {
171                         String chId = ch.getUID().getId();
172                         if (chId.equals(stateData.capabilityAttribute)) {
173                             return handler;
174                         }
175                     }
176                 }
177             }
178         }
179
180         logger.warn(
181                 "Unable to locate handler for display name: {} with attribute: {}. If this thing is included in your OpenHabAppV2 SmartApp in the Smartthings App on your phone it must also be configured in openHAB",
182                 stateData.deviceDisplayName, stateData.capabilityAttribute);
183         return null;
184     }
185
186     @Reference
187     protected void setHttpClientFactory(HttpClientFactory httpClientFactory) {
188         this.httpClient = httpClientFactory.getCommonHttpClient();
189     }
190
191     protected void unsetHttpClientFactory() {
192         this.httpClient = null;
193     }
194
195     @Nullable
196     public SmartthingsBridgeHandler getBridgeHandler() {
197         return bridgeHandler;
198     }
199
200     @Override
201     @Nullable
202     public ThingUID getBridgeUID() {
203         return bridgeHandler.getThing().getUID();
204     }
205 }