]> git.basschouten.com Git - openhab-addons.git/blob
ff0641a9e293c42bf4ca793b2537086d742fa0a3
[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.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,
59         EventHandler.class }, configurationPid = "binding.smarthings", property = "event.topics=org/openhab/binding/smartthings/state")
60 public class SmartthingsHandlerFactory extends BaseThingHandlerFactory implements ThingHandlerFactory, EventHandler {
61
62     private final Logger logger = LoggerFactory.getLogger(SmartthingsHandlerFactory.class);
63
64     private @Nullable SmartthingsBridgeHandler bridgeHandler = null;
65     private @Nullable ThingUID bridgeUID;
66     private Gson gson;
67     private List<SmartthingsThingHandler> thingHandlers = Collections.synchronizedList(new ArrayList<>());
68
69     private @NonNullByDefault({}) HttpClient httpClient;
70
71     @Override
72     public boolean supportsThingType(ThingTypeUID thingTypeUID) {
73         return THING_TYPE_SMARTTHINGS.equals(thingTypeUID) || SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
74     }
75
76     public SmartthingsHandlerFactory() {
77         // Get a Gson instance
78         gson = new Gson();
79     }
80
81     @Override
82     protected @Nullable ThingHandler createHandler(Thing thing) {
83         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
84
85         if (thingTypeUID.equals(THING_TYPE_SMARTTHINGS)) {
86             // This binding only supports one bridge. If the user tries to add a second bridge register and error and
87             // ignore
88             if (bridgeHandler != null) {
89                 logger.warn(
90                         "The Smartthings binding only supports one bridge. Please change your configuration to only use one Bridge. This bridge {} will be ignored.",
91                         thing.getUID().getAsString());
92                 return null;
93             }
94             bridgeHandler = new SmartthingsBridgeHandler((Bridge) thing, this, bundleContext);
95             bridgeUID = thing.getUID();
96             logger.debug("SmartthingsHandlerFactory created BridgeHandler for {}", thingTypeUID.getAsString());
97             return bridgeHandler;
98         } else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
99             // Everything but the bridge is handled by this one handler
100             // Make sure this thing belongs to the registered Bridge
101             if (bridgeUID != null && !bridgeUID.equals(thing.getBridgeUID())) {
102                 logger.warn("Thing: {} is being ignored because it does not belong to the registered bridge.",
103                         thing.getLabel());
104                 return null;
105             }
106             SmartthingsThingHandler thingHandler = new SmartthingsThingHandler(thing, this);
107             thingHandlers.add(thingHandler);
108             logger.debug("SmartthingsHandlerFactory created ThingHandler for {}, {}",
109                     thing.getConfiguration().get("smartthingsName"), thing.getUID().getAsString());
110             return thingHandler;
111         }
112         return null;
113     }
114
115     /**
116      * Send a command to the Smartthings Hub
117      *
118      * @param path http path which tells Smartthings what to execute
119      * @param data data to send
120      * @return Response from Smartthings
121      * @throws InterruptedException
122      * @throws TimeoutException
123      * @throws ExecutionException
124      */
125     public void sendDeviceCommand(String path, int timeout, String data)
126             throws InterruptedException, TimeoutException, ExecutionException {
127         ContentResponse response = httpClient
128                 .newRequest(bridgeHandler.getSmartthingsIp(), bridgeHandler.getSmartthingsPort())
129                 .timeout(timeout, TimeUnit.SECONDS).path(path).method(HttpMethod.POST)
130                 .content(new StringContentProvider(data), "application/json").send();
131
132         int status = response.getStatus();
133         if (status == 202) {
134             logger.debug(
135                     "Sent message \"{}\" with path \"{}\" to the Smartthings hub, received HTTP status {} (This is the normal code from Smartthings)",
136                     data, path, status);
137         } else {
138             logger.warn("Sent message \"{}\" with path \"{}\" to the Smartthings hub, received HTTP status {}", data,
139                     path, status);
140         }
141     }
142
143     /**
144      * Messages sent to the Smartthings binding from the hub via the SmartthingsServlet arrive here and are then
145      * dispatched to the correct thing's handleStateMessage function
146      *
147      * @param event The event sent
148      */
149     @Override
150     public synchronized void handleEvent(@Nullable Event event) {
151         if (event != null) {
152             String data = (String) event.getProperty("data");
153             SmartthingsStateData stateData = new SmartthingsStateData();
154             stateData = gson.fromJson(data, stateData.getClass());
155             if (stateData == null) {
156                 return;
157             }
158             SmartthingsThingHandler handler = findHandler(stateData);
159             if (handler != null) {
160                 handler.handleStateMessage(stateData);
161             }
162         }
163     }
164
165     private @Nullable SmartthingsThingHandler findHandler(SmartthingsStateData stateData) {
166         synchronized (thingHandlers) {
167             for (SmartthingsThingHandler handler : thingHandlers) {
168                 if (handler.getSmartthingsName().equals(stateData.deviceDisplayName)) {
169                     for (Channel ch : handler.getThing().getChannels()) {
170                         String chId = ch.getUID().getId();
171                         if (chId.equals(stateData.capabilityAttribute)) {
172                             return handler;
173                         }
174                     }
175                 }
176             }
177         }
178
179         logger.warn(
180                 "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",
181                 stateData.deviceDisplayName, stateData.capabilityAttribute);
182         return null;
183     }
184
185     @Reference
186     protected void setHttpClientFactory(HttpClientFactory httpClientFactory) {
187         this.httpClient = httpClientFactory.getCommonHttpClient();
188     }
189
190     protected void unsetHttpClientFactory() {
191         this.httpClient = null;
192     }
193
194     @Nullable
195     public SmartthingsBridgeHandler getBridgeHandler() {
196         return bridgeHandler;
197     }
198 }