]> git.basschouten.com Git - openhab-addons.git/blob
02c0fbefdc3d315d49be679832640261ae19fbaf
[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     @Override
127     public void sendDeviceCommand(String path, int timeout, String data)
128             throws InterruptedException, TimeoutException, ExecutionException {
129         ContentResponse response = httpClient
130                 .newRequest(bridgeHandler.getSmartthingsIp(), bridgeHandler.getSmartthingsPort())
131                 .timeout(timeout, TimeUnit.SECONDS).path(path).method(HttpMethod.POST)
132                 .content(new StringContentProvider(data), "application/json").send();
133
134         int status = response.getStatus();
135         if (status == 202) {
136             logger.debug(
137                     "Sent message \"{}\" with path \"{}\" to the Smartthings hub, received HTTP status {} (This is the normal code from Smartthings)",
138                     data, path, status);
139         } else {
140             logger.warn("Sent message \"{}\" with path \"{}\" to the Smartthings hub, received HTTP status {}", data,
141                     path, status);
142         }
143     }
144
145     /**
146      * Messages sent to the Smartthings binding from the hub via the SmartthingsServlet arrive here and are then
147      * dispatched to the correct thing's handleStateMessage function
148      *
149      * @param event The event sent
150      */
151     @Override
152     public synchronized void handleEvent(@Nullable Event event) {
153         if (event != null) {
154             String data = (String) event.getProperty("data");
155             SmartthingsStateData stateData = new SmartthingsStateData();
156             stateData = gson.fromJson(data, stateData.getClass());
157             if (stateData == null) {
158                 return;
159             }
160             SmartthingsThingHandler handler = findHandler(stateData);
161             if (handler != null) {
162                 handler.handleStateMessage(stateData);
163             }
164         }
165     }
166
167     private @Nullable SmartthingsThingHandler findHandler(SmartthingsStateData stateData) {
168         synchronized (thingHandlers) {
169             for (SmartthingsThingHandler handler : thingHandlers) {
170                 if (handler.getSmartthingsName().equals(stateData.deviceDisplayName)) {
171                     for (Channel ch : handler.getThing().getChannels()) {
172                         String chId = ch.getUID().getId();
173                         if (chId.equals(stateData.capabilityAttribute)) {
174                             return handler;
175                         }
176                     }
177                 }
178             }
179         }
180
181         logger.warn(
182                 "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",
183                 stateData.deviceDisplayName, stateData.capabilityAttribute);
184         return null;
185     }
186
187     @Reference
188     protected void setHttpClientFactory(HttpClientFactory httpClientFactory) {
189         this.httpClient = httpClientFactory.getCommonHttpClient();
190     }
191
192     protected void unsetHttpClientFactory() {
193         this.httpClient = null;
194     }
195
196     @Nullable
197     public SmartthingsBridgeHandler getBridgeHandler() {
198         return bridgeHandler;
199     }
200
201     @Override
202     @Nullable
203     public ThingUID getBridgeUID() {
204         return bridgeHandler.getThing().getUID();
205     }
206 }