added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.smartthings-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-smartthings" description="Samsung Smartthings Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.smartthings/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link SmartthingsBinding} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartthingsBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "smartthings";
|
||||
|
||||
// List of Bridge Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_SMARTTHINGS = new ThingTypeUID(BINDING_ID, "smartthings");
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
// I tried to replace this with a dynamic processing of the thing-types.xml file using the ThingTypeRegistry
|
||||
// But the HandlerFactory wants to start checking on things before that code runs. So, back to a hard coded list
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream.of(
|
||||
new ThingTypeUID(BINDING_ID, "accelerationSensor"), new ThingTypeUID(BINDING_ID, "airConditionerMode"),
|
||||
new ThingTypeUID(BINDING_ID, "alarm"), new ThingTypeUID(BINDING_ID, "battery"),
|
||||
new ThingTypeUID(BINDING_ID, "beacon"), new ThingTypeUID(BINDING_ID, "bulb"),
|
||||
new ThingTypeUID(BINDING_ID, "button"), new ThingTypeUID(BINDING_ID, "carbonDioxideMeasurement"),
|
||||
new ThingTypeUID(BINDING_ID, "carbonMonoxideDetector"), new ThingTypeUID(BINDING_ID, "color"),
|
||||
new ThingTypeUID(BINDING_ID, "colorControl"), new ThingTypeUID(BINDING_ID, "colorTemperature"),
|
||||
new ThingTypeUID(BINDING_ID, "consumable"), new ThingTypeUID(BINDING_ID, "contactSensor"),
|
||||
new ThingTypeUID(BINDING_ID, "doorControl"), new ThingTypeUID(BINDING_ID, "energyMeter"),
|
||||
new ThingTypeUID(BINDING_ID, "dryerMode"), new ThingTypeUID(BINDING_ID, "dryerOperatingState"),
|
||||
new ThingTypeUID(BINDING_ID, "estimatedTimeOfArrival"), new ThingTypeUID(BINDING_ID, "garageDoorControl"),
|
||||
new ThingTypeUID(BINDING_ID, "holdableButton"), new ThingTypeUID(BINDING_ID, "illuminanceMeasurement"),
|
||||
new ThingTypeUID(BINDING_ID, "imageCapture"), new ThingTypeUID(BINDING_ID, "indicator"),
|
||||
new ThingTypeUID(BINDING_ID, "infraredLevel"), new ThingTypeUID(BINDING_ID, "light"),
|
||||
new ThingTypeUID(BINDING_ID, "lock"), new ThingTypeUID(BINDING_ID, "lockOnly"),
|
||||
new ThingTypeUID(BINDING_ID, "mediaController"), new ThingTypeUID(BINDING_ID, "motionSensor"),
|
||||
new ThingTypeUID(BINDING_ID, "musicPlayer"), new ThingTypeUID(BINDING_ID, "outlet"),
|
||||
new ThingTypeUID(BINDING_ID, "pHMeasurement"), new ThingTypeUID(BINDING_ID, "powerMeter"),
|
||||
new ThingTypeUID(BINDING_ID, "powerSource"), new ThingTypeUID(BINDING_ID, "presenceSensor"),
|
||||
new ThingTypeUID(BINDING_ID, "relativeHumidityMeasurement"), new ThingTypeUID(BINDING_ID, "relaySwitch"),
|
||||
new ThingTypeUID(BINDING_ID, "shockSensor"), new ThingTypeUID(BINDING_ID, "signalStrength"),
|
||||
new ThingTypeUID(BINDING_ID, "sleepSensor"), new ThingTypeUID(BINDING_ID, "smokeDetector"),
|
||||
new ThingTypeUID(BINDING_ID, "soundPressureLevel"), new ThingTypeUID(BINDING_ID, "soundSensor"),
|
||||
new ThingTypeUID(BINDING_ID, "speechRecognition"), new ThingTypeUID(BINDING_ID, "stepSensor"),
|
||||
new ThingTypeUID(BINDING_ID, "switch"), new ThingTypeUID(BINDING_ID, "switchLevel"),
|
||||
new ThingTypeUID(BINDING_ID, "tamperAlert"), new ThingTypeUID(BINDING_ID, "temperatureMeasurement"),
|
||||
new ThingTypeUID(BINDING_ID, "thermostat"), new ThingTypeUID(BINDING_ID, "thermostatCoolingSetpoint"),
|
||||
new ThingTypeUID(BINDING_ID, "thermostatFanMode"),
|
||||
new ThingTypeUID(BINDING_ID, "thermostatHeatingSetpoint"), new ThingTypeUID(BINDING_ID, "thermostatMode"),
|
||||
new ThingTypeUID(BINDING_ID, "thermostatOperatingState"),
|
||||
new ThingTypeUID(BINDING_ID, "thermostatSetpoint"), new ThingTypeUID(BINDING_ID, "threeAxis"),
|
||||
new ThingTypeUID(BINDING_ID, "timedSession"), new ThingTypeUID(BINDING_ID, "touchSensor"),
|
||||
new ThingTypeUID(BINDING_ID, "ultravioletIndex"), new ThingTypeUID(BINDING_ID, "valve"),
|
||||
new ThingTypeUID(BINDING_ID, "voltageMeasurement"), new ThingTypeUID(BINDING_ID, "washerMode"),
|
||||
new ThingTypeUID(BINDING_ID, "washerOperatingState"), new ThingTypeUID(BINDING_ID, "waterSensor"),
|
||||
new ThingTypeUID(BINDING_ID, "windowShade")).collect(Collectors.toSet()));
|
||||
|
||||
// Event Handler Topics
|
||||
public static final String STATE_EVENT_TOPIC = "org/openhab/binding/smartthings/state";
|
||||
public static final String DISCOVERY_EVENT_TOPIC = "org/openhab/binding/smartthings/discovery";
|
||||
|
||||
// Bridge config properties
|
||||
public static final String IP_ADDRESS = "ipAddress";
|
||||
public static final String PORT = "port";
|
||||
|
||||
// Thing config properties
|
||||
public static final String SMARTTHINGS_NAME = "smartthingsName";
|
||||
public static final String THING_TIMEOUT = "timeout";
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal;
|
||||
|
||||
import static org.openhab.binding.smartthings.internal.SmartthingsBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
|
||||
import org.openhab.binding.smartthings.internal.handler.SmartthingsBridgeHandler;
|
||||
import org.openhab.binding.smartthings.internal.handler.SmartthingsThingHandler;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.event.Event;
|
||||
import org.osgi.service.event.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link SmartthingsHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { ThingHandlerFactory.class,
|
||||
EventHandler.class }, immediate = true, configurationPid = "binding.smarthings", property = "event.topics=org/openhab/binding/smartthings/state")
|
||||
public class SmartthingsHandlerFactory extends BaseThingHandlerFactory implements ThingHandlerFactory, EventHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartthingsHandlerFactory.class);
|
||||
|
||||
private @Nullable SmartthingsBridgeHandler bridgeHandler = null;
|
||||
private @Nullable ThingUID bridgeUID;
|
||||
private Gson gson;
|
||||
private List<SmartthingsThingHandler> thingHandlers = Collections
|
||||
.synchronizedList(new ArrayList<SmartthingsThingHandler>());
|
||||
|
||||
private @NonNullByDefault({}) HttpClient httpClient;
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return THING_TYPE_SMARTTHINGS.equals(thingTypeUID) || SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
public SmartthingsHandlerFactory() {
|
||||
// Get a Gson instance
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_SMARTTHINGS)) {
|
||||
// This binding only supports one bridge. If the user tries to add a second bridge register and error and
|
||||
// ignore
|
||||
if (bridgeHandler != null) {
|
||||
logger.warn(
|
||||
"The Smartthings binding only supports one bridge. Please change your configuration to only use one Bridge. This bridge {} will be ignored.",
|
||||
thing.getUID().getAsString());
|
||||
return null;
|
||||
}
|
||||
bridgeHandler = new SmartthingsBridgeHandler((Bridge) thing, this, bundleContext);
|
||||
bridgeUID = thing.getUID();
|
||||
logger.debug("SmartthingsHandlerFactory created BridgeHandler for {}", thingTypeUID.getAsString());
|
||||
return bridgeHandler;
|
||||
} else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
// Everything but the bridge is handled by this one handler
|
||||
// Make sure this thing belongs to the registered Bridge
|
||||
if (bridgeUID != null && !bridgeUID.equals(thing.getBridgeUID())) {
|
||||
logger.warn("Thing: {} is being ignored because it does not belong to the registered bridge.",
|
||||
thing.getLabel());
|
||||
return null;
|
||||
}
|
||||
SmartthingsThingHandler thingHandler = new SmartthingsThingHandler(thing, this);
|
||||
thingHandlers.add(thingHandler);
|
||||
logger.debug("SmartthingsHandlerFactory created ThingHandler for {}, {}",
|
||||
thing.getConfiguration().get("smartthingsName"), thing.getUID().getAsString());
|
||||
return thingHandler;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to the Smartthings Hub
|
||||
*
|
||||
* @param path http path which tells Smartthings what to execute
|
||||
* @param data data to send
|
||||
* @return Response from Smartthings
|
||||
* @throws InterruptedException
|
||||
* @throws TimeoutException
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
public void sendDeviceCommand(String path, int timeout, String data)
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
ContentResponse response = httpClient
|
||||
.newRequest(bridgeHandler.getSmartthingsIp(), bridgeHandler.getSmartthingsPort())
|
||||
.timeout(timeout, TimeUnit.SECONDS).path(path).method(HttpMethod.POST)
|
||||
.content(new StringContentProvider(data), "application/json").send();
|
||||
|
||||
int status = response.getStatus();
|
||||
if (status == 202) {
|
||||
logger.debug(
|
||||
"Sent message \"{}\" with path \"{}\" to the Smartthings hub, received HTTP status {} (This is the normal code from Smartthings)",
|
||||
data, path, status);
|
||||
} else {
|
||||
logger.warn("Sent message \"{}\" with path \"{}\" to the Smartthings hub, received HTTP status {}", data,
|
||||
path, status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Messages sent to the Smartthings binding from the hub via the SmartthingsServlet arrive here and are then
|
||||
* dispatched to the correct thing's handleStateMessage function
|
||||
*
|
||||
* @param event The event sent
|
||||
*/
|
||||
@Override
|
||||
public synchronized void handleEvent(@Nullable Event event) {
|
||||
if (event != null) {
|
||||
String data = (String) event.getProperty("data");
|
||||
SmartthingsStateData stateData = new SmartthingsStateData();
|
||||
stateData = gson.fromJson(data, stateData.getClass());
|
||||
SmartthingsThingHandler handler = findHandler(stateData);
|
||||
if (handler != null) {
|
||||
handler.handleStateMessage(stateData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable SmartthingsThingHandler findHandler(SmartthingsStateData stateData) {
|
||||
synchronized (thingHandlers) {
|
||||
for (SmartthingsThingHandler handler : thingHandlers) {
|
||||
if (handler.getSmartthingsName().equals(stateData.deviceDisplayName)) {
|
||||
for (Channel ch : handler.getThing().getChannels()) {
|
||||
String chId = ch.getUID().getId();
|
||||
if (chId.equals(stateData.capabilityAttribute)) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
"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",
|
||||
stateData.deviceDisplayName, stateData.capabilityAttribute);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setHttpClientFactory(HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
protected void unsetHttpClientFactory() {
|
||||
this.httpClient = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SmartthingsBridgeHandler getBridgeHandler() {
|
||||
return bridgeHandler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal;
|
||||
|
||||
import static org.openhab.binding.smartthings.internal.SmartthingsBindingConstants.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Dictionary;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.event.Event;
|
||||
import org.osgi.service.event.EventAdmin;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.osgi.service.http.NamespaceException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Receives all Http data from the Smartthings Hub
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@SuppressWarnings("serial")
|
||||
@Component(immediate = true, service = HttpServlet.class)
|
||||
public class SmartthingsServlet extends HttpServlet {
|
||||
private static final String PATH = "/smartthings";
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartthingsServlet.class);
|
||||
private @NonNullByDefault({}) HttpService httpService;
|
||||
private @Nullable EventAdmin eventAdmin;
|
||||
private Gson gson = new Gson();
|
||||
|
||||
@Activate
|
||||
protected void activate(Map<String, Object> config) {
|
||||
if (httpService == null) {
|
||||
logger.warn("SmartthingsServlet.activate: httpService is unexpectedly null");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Dictionary<String, String> servletParams = new Hashtable<String, String>();
|
||||
httpService.registerServlet(PATH, this, servletParams, httpService.createDefaultHttpContext());
|
||||
} catch (ServletException | NamespaceException e) {
|
||||
logger.warn("Could not start Smartthings servlet service: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate(ComponentContext componentContext) {
|
||||
if (httpService != null) {
|
||||
try {
|
||||
httpService.unregister(PATH);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setHttpService(HttpService httpService) {
|
||||
this.httpService = httpService;
|
||||
}
|
||||
|
||||
protected void unsetHttpService(HttpService httpService) {
|
||||
this.httpService = null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setEventAdmin(EventAdmin eventAdmin) {
|
||||
this.eventAdmin = eventAdmin;
|
||||
}
|
||||
|
||||
protected void unsetEventAdmin(EventAdmin eventAdmin) {
|
||||
this.eventAdmin = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
if (req == null) {
|
||||
logger.debug("SmartthingsServlet.service unexpectedly received a null request. Request not processed");
|
||||
return;
|
||||
}
|
||||
String path = req.getRequestURI();
|
||||
|
||||
// See what is in the path
|
||||
String[] pathParts = path.replace(PATH + "/", "").split("/");
|
||||
if (pathParts.length != 1) {
|
||||
logger.warn(
|
||||
"Smartthing servlet received a path with zero or more than one parts. Only one part is allowed. path {}",
|
||||
path);
|
||||
return;
|
||||
}
|
||||
|
||||
BufferedReader rdr = new BufferedReader(req.getReader());
|
||||
String s = rdr.lines().collect(Collectors.joining());
|
||||
switch (pathParts[0]) {
|
||||
case "state":
|
||||
// This is device state info returned from Smartthings
|
||||
logger.debug("Smartthing servlet processing \"state\" request. data: {}", s);
|
||||
publishEvent(STATE_EVENT_TOPIC, "data", s);
|
||||
break;
|
||||
case "discovery":
|
||||
// This is discovery data returned from Smartthings
|
||||
logger.trace("Smartthing servlet processing \"discovery\" request. data: {}", s);
|
||||
publishEvent(DISCOVERY_EVENT_TOPIC, "data", s);
|
||||
break;
|
||||
case "error":
|
||||
// This is an error message from smartthings
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map = gson.fromJson(s, map.getClass());
|
||||
logger.warn("Error message from Smartthings: {}", map.get("message"));
|
||||
break;
|
||||
default:
|
||||
logger.warn("Smartthings servlet received a path that is not supported {}", pathParts[0]);
|
||||
}
|
||||
|
||||
// A user @fx submitted a pull request stating:
|
||||
// It appears that the HubAction queue will choke for a timeout of 6-8s~ if a http action doesn't return a body
|
||||
// (or possibly on the 204 http code, I didn't test them separately.)
|
||||
// I tested the following scenarios:
|
||||
// 1. Return status 204 with a response of OK
|
||||
// 2. Return status 202 with no response
|
||||
// 3. No response.
|
||||
// In all cases the time was about the same - 3.5 sec/request
|
||||
// Both the 202 and 204 responses resulted in the hub logging an error: received a request with an unknown path:
|
||||
// HTTP/1.1 200 OK, content-Length: 0
|
||||
// Therefore I am opting to return nothing since no error message occurs.
|
||||
// resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||
// resp.setStatus(HttpServletResponse.SC_OK);
|
||||
// resp.getWriter().write("OK");
|
||||
// resp.getWriter().flush();
|
||||
// resp.getWriter().close();
|
||||
logger.trace("Smartthings servlet returning.");
|
||||
return;
|
||||
}
|
||||
|
||||
private void publishEvent(String topic, String name, String data) {
|
||||
Dictionary<String, String> props = new Hashtable<String, String>();
|
||||
props.put(name, data);
|
||||
Event event = new Event(topic, props);
|
||||
if (eventAdmin != null) {
|
||||
eventAdmin.postEvent(event);
|
||||
} else {
|
||||
logger.debug("SmartthingsServlet:publishEvent eventAdmin is unexpectedly null");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.converter;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Converter class for Smartthings capability "Color Control".
|
||||
* In this case the color being delivered by Smartthings is in the for #hhssbb where hh=hue in hex, ss=saturation in hex
|
||||
* and bb=brightness in hex
|
||||
* And, the hue is a value from 0 to 100% but openHAB expects the hue in 0 to 360
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartthingsColor100Converter extends SmartthingsConverter {
|
||||
|
||||
private Pattern rgbInputPattern = Pattern.compile("^#[0-9a-fA-F]{6}");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartthingsColor100Converter.class);
|
||||
|
||||
public SmartthingsColor100Converter(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToSmartthings(ChannelUID channelUid, Command command) {
|
||||
String jsonMsg;
|
||||
// The command should be of HSBType. The hue component needs to be divided by 3.6 to convert 0-360 degrees to
|
||||
// 0-100 percent
|
||||
// The easiest way to do this is to create a new HSBType with the hue component changed.
|
||||
if (command instanceof HSBType) {
|
||||
HSBType hsb = (HSBType) command;
|
||||
double hue = Math.round((hsb.getHue().doubleValue() / 3.60)); // add .5 to round
|
||||
long hueInt = (long) hue;
|
||||
HSBType hsb100 = new HSBType(new DecimalType(hueInt), hsb.getSaturation(), hsb.getBrightness());
|
||||
// now use the default converter to convert to a JSON string
|
||||
jsonMsg = defaultConvertToSmartthings(channelUid, hsb100);
|
||||
} else {
|
||||
jsonMsg = defaultConvertToSmartthings(channelUid, command);
|
||||
}
|
||||
return jsonMsg;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.openhab.binding.smartthings.internal.converter.SmartthingsConverter#convertToOpenHab(java.lang.String,
|
||||
* org.openhab.binding.smartthings.internal.SmartthingsStateData)
|
||||
*/
|
||||
@Override
|
||||
public State convertToOpenHab(@Nullable String acceptedChannelType, SmartthingsStateData dataFromSmartthings) {
|
||||
// The color value from Smartthings will look like "#123456" which is the RGB color
|
||||
// This needs to be converted into HSB type
|
||||
String value = dataFromSmartthings.value;
|
||||
if (value == null) {
|
||||
logger.warn("Failed to convert color {} because Smartthings returned a null value.",
|
||||
dataFromSmartthings.deviceDisplayName);
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
// If the bulb is off the value maybe null, so better check
|
||||
State state;
|
||||
// First verify the format the string is valid
|
||||
Matcher matcher = rgbInputPattern.matcher(value);
|
||||
if (!matcher.matches()) {
|
||||
logger.warn(
|
||||
"The \"value\" in the following message is not a valid color. Expected a value like \"#123456\" instead of {}",
|
||||
dataFromSmartthings.toString());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
// Get the RGB colors
|
||||
int rgb[] = new int[3];
|
||||
for (int i = 0, pos = 1; i < 3; i++, pos += 2) {
|
||||
String c = value.substring(pos, pos + 2);
|
||||
rgb[i] = Integer.parseInt(c, 16);
|
||||
}
|
||||
|
||||
// Convert to state
|
||||
state = HSBType.fromRGB(rgb[0], rgb[1], rgb[2]);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.converter;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Converter class for Smartthings "Color" capability and not the "Color Control" capability.
|
||||
* The Smartthings Color capability seems to be a later capability where the hue is in the standard 0 - 360 range and
|
||||
* therefore doesn't need to be converted for openHAB
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartthingsColorConverter extends SmartthingsConverter {
|
||||
|
||||
private Pattern rgbInputPattern = Pattern.compile("^#[0-9a-fA-F]{6}");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartthingsColorConverter.class);
|
||||
|
||||
public SmartthingsColorConverter(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToSmartthings(ChannelUID channelUid, Command command) {
|
||||
String jsonMsg = defaultConvertToSmartthings(channelUid, command);
|
||||
return jsonMsg;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.openhab.binding.smartthings.internal.converter.SmartthingsConverter#convertToOpenHab(java.lang.String,
|
||||
* org.openhab.binding.smartthings.internal.SmartthingsStateData)
|
||||
*/
|
||||
@Override
|
||||
public State convertToOpenHab(@Nullable String acceptedChannelType, SmartthingsStateData dataFromSmartthings) {
|
||||
// The color value from Smartthings will look like "#123456" which is the RGB color
|
||||
// This needs to be converted into HSB type
|
||||
String value = dataFromSmartthings.value;
|
||||
if (value == null) {
|
||||
logger.warn("Failed to convert color {} because Smartthings returned a null value.",
|
||||
dataFromSmartthings.deviceDisplayName);
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
// First verify the format the string is valid
|
||||
Matcher matcher = rgbInputPattern.matcher(value);
|
||||
if (!matcher.matches()) {
|
||||
logger.warn(
|
||||
"The \"value\" in the following message is not a valid color. Expected a value like \"#123456\" instead of {}",
|
||||
dataFromSmartthings.toString());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
// Get the RGB colors
|
||||
int rgb[] = new int[3];
|
||||
for (int i = 0, pos = 1; i < 3; i++, pos += 2) {
|
||||
String c = value.substring(pos, pos + 2);
|
||||
rgb[i] = Integer.parseInt(c, 16);
|
||||
}
|
||||
|
||||
// Convert to state
|
||||
State state = HSBType.fromRGB(rgb[0], rgb[1], rgb[2]);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.converter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
|
||||
import org.openhab.binding.smartthings.internal.handler.SmartthingsThingConfig;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.NextPreviousType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.RewindFastforwardType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.StringListType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base converter class.
|
||||
* The converter classes are responsible for converting "state" messages from the smartthings hub into openHAB States.
|
||||
* And, converting handler.handleCommand() into messages to be sent to smartthings
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class SmartthingsConverter {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartthingsConverter.class);
|
||||
|
||||
protected String smartthingsName;
|
||||
protected String thingTypeId;
|
||||
|
||||
SmartthingsConverter(Thing thing) {
|
||||
smartthingsName = thing.getConfiguration().as(SmartthingsThingConfig.class).smartthingsName;
|
||||
thingTypeId = thing.getThingTypeUID().getId();
|
||||
}
|
||||
|
||||
public abstract String convertToSmartthings(ChannelUID channelUid, Command command);
|
||||
|
||||
public abstract State convertToOpenHab(@Nullable String acceptedChannelType,
|
||||
SmartthingsStateData dataFromSmartthings);
|
||||
|
||||
/**
|
||||
* Provide a default converter in the base call so it can be used in sub-classes if needed
|
||||
*
|
||||
* @param command
|
||||
* @return The json string to send to Smartthings
|
||||
*/
|
||||
protected String defaultConvertToSmartthings(ChannelUID channelUid, Command command) {
|
||||
String value;
|
||||
|
||||
if (command instanceof DateTimeType) {
|
||||
DateTimeType dt = (DateTimeType) command;
|
||||
value = dt.format("%m/%d/%Y %H.%M.%S");
|
||||
} else if (command instanceof HSBType) {
|
||||
HSBType hsb = (HSBType) command;
|
||||
value = String.format("[%d, %d, %d ]", hsb.getHue().intValue(), hsb.getSaturation().intValue(),
|
||||
hsb.getBrightness().intValue());
|
||||
} else if (command instanceof DecimalType) {
|
||||
value = command.toString();
|
||||
} else if (command instanceof IncreaseDecreaseType) { // Need to surround with double quotes
|
||||
value = surroundWithQuotes(command.toString().toLowerCase());
|
||||
} else if (command instanceof NextPreviousType) { // Need to surround with double quotes
|
||||
value = surroundWithQuotes(command.toString().toLowerCase());
|
||||
} else if (command instanceof OnOffType) { // Need to surround with double quotes
|
||||
value = surroundWithQuotes(command.toString().toLowerCase());
|
||||
} else if (command instanceof OpenClosedType) { // Need to surround with double quotes
|
||||
value = surroundWithQuotes(command.toString().toLowerCase());
|
||||
} else if (command instanceof PercentType) {
|
||||
value = command.toString();
|
||||
} else if (command instanceof PointType) { // There is not a comparable type in Smartthings, log and send value
|
||||
logger.warn(
|
||||
"Warning - PointType Command is not supported by Smartthings. Please configure to use a different command type. CapabilityKey: {}, displayName: {}, capabilityAttribute {}",
|
||||
thingTypeId, smartthingsName, channelUid.getId());
|
||||
value = command.toFullString();
|
||||
} else if (command instanceof RefreshType) { // Need to surround with double quotes
|
||||
value = surroundWithQuotes(command.toString().toLowerCase());
|
||||
} else if (command instanceof RewindFastforwardType) { // Need to surround with double quotes
|
||||
value = surroundWithQuotes(command.toString().toLowerCase());
|
||||
} else if (command instanceof StopMoveType) { // Need to surround with double quotes
|
||||
value = surroundWithQuotes(command.toString().toLowerCase());
|
||||
} else if (command instanceof PlayPauseType) { // Need to surround with double quotes
|
||||
value = surroundWithQuotes(command.toString().toLowerCase());
|
||||
} else if (command instanceof StringListType) {
|
||||
value = surroundWithQuotes(command.toString());
|
||||
} else if (command instanceof StringType) {
|
||||
value = surroundWithQuotes(command.toString());
|
||||
} else if (command instanceof UpDownType) { // Need to surround with double quotes
|
||||
value = surroundWithQuotes(command.toString().toLowerCase());
|
||||
} else {
|
||||
logger.warn(
|
||||
"Warning - The Smartthings converter does not know how to handle the {} command. The Smartthingsonverter class should be updated. CapabilityKey: {}, displayName: {}, capabilityAttribute {}",
|
||||
command.getClass().getName(), thingTypeId, smartthingsName, channelUid.getId());
|
||||
value = command.toString().toLowerCase();
|
||||
}
|
||||
|
||||
String jsonMsg = String.format(
|
||||
"{\"capabilityKey\": \"%s\", \"deviceDisplayName\": \"%s\", \"capabilityAttribute\": \"%s\", \"value\": %s}",
|
||||
thingTypeId, smartthingsName, channelUid.getId(), value);
|
||||
|
||||
return jsonMsg;
|
||||
}
|
||||
|
||||
protected String surroundWithQuotes(String param) {
|
||||
return (new StringBuilder()).append('"').append(param).append('"').toString();
|
||||
}
|
||||
|
||||
protected State defaultConvertToOpenHab(@Nullable String acceptedChannelType,
|
||||
SmartthingsStateData dataFromSmartthings) {
|
||||
// If there is no stateMap the just return null State
|
||||
if (acceptedChannelType == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
String deviceType = dataFromSmartthings.capabilityAttribute;
|
||||
Object deviceValue = dataFromSmartthings.value;
|
||||
|
||||
// deviceValue can be null, handle that up front
|
||||
if (deviceValue == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
switch (acceptedChannelType) {
|
||||
case "Color":
|
||||
logger.warn(
|
||||
"Conversion of Color is not supported by the default Smartthings to opemHAB converter. The ThingType should specify an appropriate converter. Device name: {}, Attribute: {}.",
|
||||
dataFromSmartthings.deviceDisplayName, deviceType);
|
||||
return UnDefType.UNDEF;
|
||||
case "Contact":
|
||||
return "open".equals(deviceValue) ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
|
||||
case "DateTime":
|
||||
return UnDefType.UNDEF;
|
||||
case "Dimmer":
|
||||
// The value coming in should be a number
|
||||
if (deviceValue instanceof String) {
|
||||
return new PercentType((String) deviceValue);
|
||||
} else {
|
||||
logger.warn("Failed to convert {} with a value of {} from class {} to an appropriate type.",
|
||||
deviceType, deviceValue, deviceValue.getClass().getName());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
case "Number":
|
||||
if (deviceValue instanceof String) {
|
||||
return new DecimalType(Double.parseDouble((String) deviceValue));
|
||||
} else if (deviceValue instanceof Double) {
|
||||
return new DecimalType((Double) deviceValue);
|
||||
} else if (deviceValue instanceof Long) {
|
||||
return new DecimalType((Long) deviceValue);
|
||||
} else {
|
||||
logger.warn("Failed to convert Number {} with a value of {} from class {} to an appropriate type.",
|
||||
deviceType, deviceValue, deviceValue.getClass().getName());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
case "Player":
|
||||
logger.warn("Conversion of Player is not currently supported. Need to provide support for message {}.",
|
||||
deviceValue);
|
||||
return UnDefType.UNDEF;
|
||||
case "Rollershutter":
|
||||
return "open".equals(deviceValue) ? UpDownType.DOWN : UpDownType.UP;
|
||||
case "String":
|
||||
return new StringType((String) deviceValue);
|
||||
case "Switch":
|
||||
return "on".equals(deviceValue) ? OnOffType.ON : OnOffType.OFF;
|
||||
|
||||
// Vector3 can't be triggered now but keep it to handle acceleration device
|
||||
case "Vector3":
|
||||
// This is a weird result from Smartthings. If the messages is from a "state" request the result will
|
||||
// look like: "value":{"z":22,"y":-36,"x":-987}
|
||||
// But if the result is from sensor change via a subscription to a a threeAxis device the results will
|
||||
// be a String of the format "value":"-873,-70,484"
|
||||
// which GSON returns as a LinkedTreeMap
|
||||
if (deviceValue instanceof String) {
|
||||
return new StringType((String) deviceValue);
|
||||
} else if (deviceValue instanceof Map<?, ?>) {
|
||||
Map<String, String> map = (Map<String, String>) deviceValue;
|
||||
String s = String.format("%.0f,%.0f,%.0f", map.get("x"), map.get("y"), map.get("z"));
|
||||
return new StringType(s);
|
||||
} else {
|
||||
logger.warn(
|
||||
"Unable to convert {} which should be in Smartthings Vector3 format to a string. The returned datatype from Smartthings is {}.",
|
||||
deviceType, deviceValue.getClass().getName());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
default:
|
||||
logger.warn("No type defined to convert {} with a value of {} from class {} to an appropriate type.",
|
||||
deviceType, deviceValue, deviceValue.getClass().getName());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.converter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* This "Converter" is assigned to a channel when a special converter is not needed.
|
||||
* A channel specific converter is specified in the thing-type channel property smartthings-converter then that channel
|
||||
* is used.
|
||||
* If a channel specific converter is not found a convert based on the channel ID is used.
|
||||
* If there is no convert found then this Default converter is used.
|
||||
* Yes, it would be possible to change the SamrtthingsConverter class to not being abstract and implement these methods
|
||||
* there. But, this makes it explicit that the default converter is being used.
|
||||
* See SmartthingsThingHandler.initialize() for details
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartthingsDefaultConverter extends SmartthingsConverter {
|
||||
|
||||
public SmartthingsDefaultConverter(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToSmartthings(ChannelUID channelUid, Command command) {
|
||||
String jsonMsg = defaultConvertToSmartthings(channelUid, command);
|
||||
return jsonMsg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State convertToOpenHab(@Nullable String acceptedChannelType, SmartthingsStateData dataFromSmartthings) {
|
||||
State state = defaultConvertToOpenHab(acceptedChannelType, dataFromSmartthings);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.converter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Converter class for Smartthings capability "Color Control".
|
||||
* The Smartthings "Color Control" capability represents the hue values in the 0-100% range. OH2 uses 0-360 degrees
|
||||
* For this converter only the hue is coming into openHAB and it is a number
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartthingsHue100Converter extends SmartthingsConverter {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartthingsHue100Converter.class);
|
||||
|
||||
public SmartthingsHue100Converter(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToSmartthings(ChannelUID channelUid, Command command) {
|
||||
String jsonMsg;
|
||||
|
||||
if (command instanceof HSBType) {
|
||||
HSBType hsb = (HSBType) command;
|
||||
double hue = hsb.getHue().doubleValue() / 3.60;
|
||||
String value = String.format("[%.0f, %d, %d ]", hue, hsb.getSaturation().intValue(),
|
||||
hsb.getBrightness().intValue());
|
||||
jsonMsg = String.format(
|
||||
"{\"capabilityKey\": \"%s\", \"deviceDisplayName\": \"%s\", \"capabilityAttribute\": \"%s\", \"value\": %s}",
|
||||
thingTypeId, smartthingsName, channelUid.getId(), value);
|
||||
} else {
|
||||
jsonMsg = defaultConvertToSmartthings(channelUid, command);
|
||||
}
|
||||
|
||||
return jsonMsg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State convertToOpenHab(@Nullable String acceptedChannelType, SmartthingsStateData dataFromSmartthings) {
|
||||
// Here we have to multiply the value from Smartthings by 3.6 to convert from 0-100 to 0-360
|
||||
String deviceType = dataFromSmartthings.capabilityAttribute;
|
||||
Object deviceValue = dataFromSmartthings.value;
|
||||
|
||||
if (deviceValue == null) {
|
||||
logger.warn("Failed to convert Number {} because Smartthings returned a null value.", deviceType);
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
if ("Number".contentEquals(acceptedChannelType)) {
|
||||
if (deviceValue instanceof String) {
|
||||
double d = Double.parseDouble((String) deviceValue);
|
||||
d *= 3.6;
|
||||
return new DecimalType(d);
|
||||
} else if (deviceValue instanceof Long) {
|
||||
double d = ((Long) deviceValue).longValue();
|
||||
d *= 3.6;
|
||||
return new DecimalType(d);
|
||||
} else if (deviceValue instanceof BigDecimal) {
|
||||
double d = ((BigDecimal) deviceValue).doubleValue();
|
||||
d *= 3.6;
|
||||
return new DecimalType(d);
|
||||
} else if (deviceValue instanceof Number) {
|
||||
double d = ((Number) deviceValue).doubleValue();
|
||||
d *= 3.6;
|
||||
return new DecimalType(d);
|
||||
} else {
|
||||
logger.warn("Failed to convert Number {} with a value of {} from class {} to an appropriate type.",
|
||||
deviceType, deviceValue, deviceValue.getClass().getName());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
} else {
|
||||
return defaultConvertToOpenHab(acceptedChannelType, dataFromSmartthings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.converter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* Converter class for Door Control.
|
||||
* This can't use the default because when closing the door the command that comes in as "closed" but "close" needs to
|
||||
* be
|
||||
* sent to Smartthings
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartthingsOpenCloseControlConverter extends SmartthingsConverter {
|
||||
|
||||
public SmartthingsOpenCloseControlConverter(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToSmartthings(ChannelUID channelUid, Command command) {
|
||||
String smartthingsValue = (command.toString().toLowerCase().equals("open")) ? "open" : "close";
|
||||
smartthingsValue = surroundWithQuotes(smartthingsValue);
|
||||
|
||||
String jsonMsg = String.format("{\"capabilityKey\": \"%s\", \"deviceDisplayName\": \"%s\", \"value\": %s}",
|
||||
thingTypeId, smartthingsName, smartthingsValue);
|
||||
|
||||
return jsonMsg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State convertToOpenHab(@Nullable String acceptedChannelType, SmartthingsStateData dataFromSmartthings) {
|
||||
State state = defaultConvertToOpenHab(acceptedChannelType, dataFromSmartthings);
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.discovery;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smartthings.internal.SmartthingsBindingConstants;
|
||||
import org.openhab.binding.smartthings.internal.SmartthingsHandlerFactory;
|
||||
import org.openhab.binding.smartthings.internal.dto.SmartthingsDeviceData;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.event.Event;
|
||||
import org.osgi.service.event.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Smartthings Discovery service
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { DiscoveryService.class,
|
||||
EventHandler.class }, immediate = true, configurationPid = "discovery.smartthings", property = "event.topics=org/openhab/binding/smartthings/discovery")
|
||||
public class SmartthingsDiscoveryService extends AbstractDiscoveryService implements EventHandler {
|
||||
private static final int DISCOVERY_TIMEOUT_SEC = 30;
|
||||
private static final int INITIAL_DELAY_SEC = 10; // Delay 10 sec to give time for bridge and things to be created
|
||||
private static final int SCAN_INTERVAL_SEC = 600;
|
||||
|
||||
private final Pattern findIllegalChars = Pattern.compile("[^A-Za-z0-9_-]");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartthingsDiscoveryService.class);
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
private @Nullable SmartthingsHandlerFactory smartthingsHandlerFactory;
|
||||
|
||||
private @Nullable ScheduledFuture<?> scanningJob;
|
||||
|
||||
/*
|
||||
* default constructor
|
||||
*/
|
||||
public SmartthingsDiscoveryService() {
|
||||
super(SmartthingsBindingConstants.SUPPORTED_THING_TYPES_UIDS, DISCOVERY_TIMEOUT_SEC);
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setThingHandlerFactory(ThingHandlerFactory handlerFactory) {
|
||||
if (handlerFactory instanceof SmartthingsHandlerFactory) {
|
||||
smartthingsHandlerFactory = (SmartthingsHandlerFactory) handlerFactory;
|
||||
}
|
||||
}
|
||||
|
||||
protected void unsetThingHandlerFactory(ThingHandlerFactory handlerFactory) {
|
||||
// Make sure it is this handleFactory that should be unset
|
||||
if (handlerFactory == smartthingsHandlerFactory) {
|
||||
this.smartthingsHandlerFactory = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the UI when starting a search.
|
||||
*/
|
||||
@Override
|
||||
public void startScan() {
|
||||
sendSmartthingsDiscoveryRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a running scan.
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
removeOlderResults(getTimestampOfLastScan());
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts background scanning for attached devices.
|
||||
*/
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
if (scanningJob == null) {
|
||||
this.scanningJob = scheduler.scheduleWithFixedDelay(this::sendSmartthingsDiscoveryRequest,
|
||||
INITIAL_DELAY_SEC, SCAN_INTERVAL_SEC, TimeUnit.SECONDS);
|
||||
logger.debug("Discovery background scanning job started");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops background scanning for attached devices.
|
||||
*/
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
final ScheduledFuture<?> currentScanningJob = scanningJob;
|
||||
if (currentScanningJob != null) {
|
||||
currentScanningJob.cancel(false);
|
||||
scanningJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the discovery process by sending a discovery request to the Smartthings Hub
|
||||
*/
|
||||
private void sendSmartthingsDiscoveryRequest() {
|
||||
final SmartthingsHandlerFactory currentSmartthingsHandlerFactory = smartthingsHandlerFactory;
|
||||
if (currentSmartthingsHandlerFactory != null) {
|
||||
try {
|
||||
String discoveryMsg = "{\"discovery\": \"yes\"}";
|
||||
currentSmartthingsHandlerFactory.sendDeviceCommand("/discovery", 5, discoveryMsg);
|
||||
// Smartthings will not return a response to this message but will send it's response message
|
||||
// which will get picked up by the SmartthingBridgeHandler.receivedPushMessage handler
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.warn("Attempt to send command to the Smartthings hub failed with: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle discovery data returned from the Smartthings hub.
|
||||
* The data is delivered into the SmartthingServlet. From there it is sent here via the Event service
|
||||
*/
|
||||
@Override
|
||||
public void handleEvent(@Nullable Event event) {
|
||||
if (event == null) {
|
||||
logger.info("SmartthingsDiscoveryService.handleEvent: event is uexpectedly null");
|
||||
return;
|
||||
}
|
||||
String topic = event.getTopic();
|
||||
String data = (String) event.getProperty("data");
|
||||
if (data == null) {
|
||||
logger.debug("Event received on topic: {} but the data field is null", topic);
|
||||
return;
|
||||
} else {
|
||||
logger.trace("Event received on topic: {}", topic);
|
||||
}
|
||||
|
||||
// The data returned from the Smartthings hub is a list of strings where each
|
||||
// element is the data for one device. That device string is another json object
|
||||
List<String> devices = new ArrayList<String>();
|
||||
devices = gson.fromJson(data, devices.getClass());
|
||||
for (String device : devices) {
|
||||
SmartthingsDeviceData deviceData = gson.fromJson(device, SmartthingsDeviceData.class);
|
||||
createDevice(deviceData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a device with the data from the Smartthings hub
|
||||
*
|
||||
* @param deviceData Device data from the hub
|
||||
*/
|
||||
private void createDevice(SmartthingsDeviceData deviceData) {
|
||||
logger.trace("Discovery: Creating device: ThingType {} with name {}", deviceData.capability, deviceData.name);
|
||||
|
||||
// Build the UID as a string smartthings:{ThingType}:{BridgeName}:{DeviceName}
|
||||
String name = deviceData.name; // Note: this is necessary for null analysis to work
|
||||
if (name == null) {
|
||||
logger.info(
|
||||
"Unexpectedly received data for a device with no name. Check the Smartthings hub devices and make sure every device has a name");
|
||||
return;
|
||||
}
|
||||
String deviceNameNoSpaces = name.replaceAll("\\s", "_");
|
||||
String smartthingsDeviceName = findIllegalChars.matcher(deviceNameNoSpaces).replaceAll("");
|
||||
final SmartthingsHandlerFactory currentSmartthingsHandlerFactory = smartthingsHandlerFactory;
|
||||
if (currentSmartthingsHandlerFactory == null) {
|
||||
logger.info(
|
||||
"SmartthingsDiscoveryService: smartthingshandlerfactory is unexpectedly null, could not create device {}",
|
||||
deviceData);
|
||||
return;
|
||||
}
|
||||
ThingUID bridgeUid = currentSmartthingsHandlerFactory.getBridgeHandler().getThing().getUID();
|
||||
String bridgeId = bridgeUid.getId();
|
||||
String uidStr = String.format("smartthings:%s:%s:%s", deviceData.capability, bridgeId, smartthingsDeviceName);
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("smartthingsName", name);
|
||||
properties.put("deviceId", deviceData.id);
|
||||
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(new ThingUID(uidStr)).withProperties(properties)
|
||||
.withRepresentationProperty("deviceId").withBridge(bridgeUid).withLabel(name).build();
|
||||
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.dto;
|
||||
|
||||
/**
|
||||
* Mapping object for data returned from smartthings hub
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
public class SmartthingsDeviceData {
|
||||
public String capability;
|
||||
public String attribute;
|
||||
public String name;
|
||||
public String id;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("capability :").append(capability);
|
||||
sb.append(", attribute :").append(attribute);
|
||||
sb.append(", name: ").append(name);
|
||||
sb.append(", id: ").append(id);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.dto;
|
||||
|
||||
/**
|
||||
* Data object for smartthings state data
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
public class SmartthingsStateData {
|
||||
public String deviceDisplayName;
|
||||
public String capabilityAttribute;
|
||||
public String value;
|
||||
|
||||
public SmartthingsStateData() {
|
||||
// These values will always be overridden when the object is initialized by GSon
|
||||
deviceDisplayName = "";
|
||||
capabilityAttribute = "";
|
||||
value = "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(", deviceDisplayName :").append(deviceDisplayName);
|
||||
sb.append(", capabilityAttribute :").append(capabilityAttribute);
|
||||
sb.append(", value :").append(value);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Configuration data for Smartthings hub
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartthingsBridgeConfig {
|
||||
|
||||
/**
|
||||
* IP address of smartthings hub
|
||||
*/
|
||||
public String smartthingsIp = "";
|
||||
|
||||
/**
|
||||
* Port number of smartthings hub
|
||||
*/
|
||||
public int smartthingsPort = -1;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("smartthingsIp = ").append(smartthingsIp);
|
||||
sb.append(", smartthingsPort = ").append(smartthingsPort);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Smartthings Bridge messages
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SmartthingsBridgeConfigStatusMessage {
|
||||
|
||||
static final String IP_MISSING = "missing-ip-configuration";
|
||||
static final String PORT_MISSING = "missing-port-configuration";
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.handler;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.smartthings.internal.SmartthingsBindingConstants;
|
||||
import org.openhab.binding.smartthings.internal.SmartthingsHandlerFactory;
|
||||
import org.openhab.core.config.core.status.ConfigStatusMessage;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SmartthingsBridgeHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartthingsBridgeHandler extends ConfigStatusBridgeHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartthingsBridgeHandler.class);
|
||||
|
||||
private SmartthingsBridgeConfig config;
|
||||
|
||||
private SmartthingsHandlerFactory smartthingsHandlerFactory;
|
||||
private BundleContext bundleContext;
|
||||
|
||||
public SmartthingsBridgeHandler(Bridge bridge, SmartthingsHandlerFactory smartthingsHandlerFactory,
|
||||
BundleContext bundleContext) {
|
||||
super(bridge);
|
||||
this.smartthingsHandlerFactory = smartthingsHandlerFactory;
|
||||
this.bundleContext = bundleContext;
|
||||
config = getThing().getConfiguration().as(SmartthingsBridgeConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Commands are handled by the "Things"
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// Validate the config
|
||||
if (!validateConfig(this.config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private boolean validateConfig(SmartthingsBridgeConfig config) {
|
||||
if (config.smartthingsIp.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Smartthings IP address is not specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.smartthingsPort <= 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Smartthings Port is not specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public SmartthingsHandlerFactory getSmartthingsHandlerFactory() {
|
||||
return smartthingsHandlerFactory;
|
||||
}
|
||||
|
||||
public BundleContext getBundleContext() {
|
||||
return bundleContext;
|
||||
}
|
||||
|
||||
public String getSmartthingsIp() {
|
||||
return config.smartthingsIp;
|
||||
}
|
||||
|
||||
public int getSmartthingsPort() {
|
||||
return config.smartthingsPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigStatusMessage> getConfigStatus() {
|
||||
Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<ConfigStatusMessage>();
|
||||
|
||||
// The IP must be provided
|
||||
String ip = config.smartthingsIp;
|
||||
if (ip.isEmpty()) {
|
||||
configStatusMessages.add(ConfigStatusMessage.Builder.error(SmartthingsBindingConstants.IP_ADDRESS)
|
||||
.withMessageKeySuffix(SmartthingsBridgeConfigStatusMessage.IP_MISSING)
|
||||
.withArguments(SmartthingsBindingConstants.IP_ADDRESS).build());
|
||||
}
|
||||
|
||||
// The PORT must be provided
|
||||
int port = config.smartthingsPort;
|
||||
if (port <= 0) {
|
||||
configStatusMessages.add(ConfigStatusMessage.Builder.error(SmartthingsBindingConstants.PORT)
|
||||
.withMessageKeySuffix(SmartthingsBridgeConfigStatusMessage.PORT_MISSING)
|
||||
.withArguments(SmartthingsBindingConstants.PORT).build());
|
||||
}
|
||||
|
||||
return configStatusMessages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Configuration data for Smartthings device
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartthingsThingConfig {
|
||||
|
||||
/**
|
||||
* The user assigned name used in the Smartthings hub (required)
|
||||
*/
|
||||
public String smartthingsName = "";
|
||||
/**
|
||||
* The device location (optional)
|
||||
*/
|
||||
public String smartthingsLocation = "";
|
||||
/**
|
||||
* Timeout (defaults to 3 seconds)
|
||||
*/
|
||||
public int smartthingsTimeout = 3;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Smartthings Bridge messages
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SmartthingsThingConfigStatusMessage {
|
||||
|
||||
static final String SMARTTHINGS_NAME_MISSING = "missing-smartthings-name";
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smartthings.internal.handler;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.smartthings.internal.SmartthingsBindingConstants;
|
||||
import org.openhab.binding.smartthings.internal.SmartthingsHandlerFactory;
|
||||
import org.openhab.binding.smartthings.internal.converter.SmartthingsConverter;
|
||||
import org.openhab.binding.smartthings.internal.dto.SmartthingsStateData;
|
||||
import org.openhab.core.config.core.status.ConfigStatusMessage;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.ConfigStatusThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmartthingsThingHandler extends ConfigStatusThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartthingsThingHandler.class);
|
||||
|
||||
private SmartthingsThingConfig config;
|
||||
private String smartthingsName;
|
||||
private int timeout;
|
||||
private SmartthingsHandlerFactory smartthingsHandlerFactory;
|
||||
private Map<ChannelUID, SmartthingsConverter> converters = new HashMap<ChannelUID, SmartthingsConverter>();
|
||||
|
||||
private final String smartthingsConverterName = "smartthings-converter";
|
||||
|
||||
public SmartthingsThingHandler(Thing thing, SmartthingsHandlerFactory smartthingsHandlerFactory) {
|
||||
super(thing);
|
||||
this.smartthingsHandlerFactory = smartthingsHandlerFactory;
|
||||
smartthingsName = ""; // Initialize here so it can be NonNull but it should always get a value in initialize()
|
||||
config = new SmartthingsThingConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when openHAB receives a command for this handler
|
||||
*
|
||||
* @param channelUID The channel the command was sent to
|
||||
* @param command The command sent
|
||||
*/
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
Bridge bridge = getBridge();
|
||||
|
||||
// Check if the bridge has not been initialized yet
|
||||
if (bridge == null) {
|
||||
logger.debug(
|
||||
"The bridge has not been initialized yet. Can not process command for channel {} with command {}.",
|
||||
channelUID.getAsString(), command.toFullString());
|
||||
return;
|
||||
}
|
||||
|
||||
SmartthingsBridgeHandler smartthingsBridgeHandler = (SmartthingsBridgeHandler) bridge.getHandler();
|
||||
if (smartthingsBridgeHandler != null
|
||||
&& smartthingsBridgeHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
|
||||
String thingTypeId = thing.getThingTypeUID().getId();
|
||||
String smartthingsType = getSmartthingsAttributeFromChannel(channelUID);
|
||||
|
||||
SmartthingsConverter converter = converters.get(channelUID);
|
||||
|
||||
String path;
|
||||
String jsonMsg;
|
||||
if (command instanceof RefreshType) {
|
||||
path = "/state";
|
||||
// Go to ST hub and ask for current state
|
||||
jsonMsg = String.format(
|
||||
"{\"capabilityKey\": \"%s\", \"deviceDisplayName\": \"%s\", \"capabilityAttribute\": \"%s\", \"openHabStartTime\": %d}",
|
||||
thingTypeId, smartthingsName, smartthingsType, System.currentTimeMillis());
|
||||
} else {
|
||||
// Send update to ST hub
|
||||
path = "/update";
|
||||
jsonMsg = converter.convertToSmartthings(channelUID, command);
|
||||
|
||||
// The smartthings hub won't (can't) return a response to this call. But, it will send a separate
|
||||
// message back to the SmartthingBridgeHandler.receivedPushMessage handler
|
||||
}
|
||||
|
||||
try {
|
||||
smartthingsHandlerFactory.sendDeviceCommand(path, timeout, jsonMsg);
|
||||
// Smartthings will not return a response to this message but will send it's response message
|
||||
// which will get picked up by the SmartthingBridgeHandler.receivedPushMessage handler
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.warn("Attempt to send command to the Smartthings hub for {} failed with exception: {}",
|
||||
smartthingsName, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Smartthings capability reference "attribute" from the channel properties.
|
||||
* In OpenHAB each channel id corresponds to the Smartthings attribute. In the ChannelUID the
|
||||
* channel id is the last segment
|
||||
*
|
||||
* @param channelUID
|
||||
* @return channel id
|
||||
*/
|
||||
private String getSmartthingsAttributeFromChannel(ChannelUID channelUID) {
|
||||
String id = channelUID.getId();
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* State messages sent from the hub arrive here, are processed and the openHab state is updated.
|
||||
*
|
||||
* @param stateData
|
||||
*/
|
||||
public void handleStateMessage(SmartthingsStateData stateData) {
|
||||
// First locate the channel
|
||||
Channel matchingChannel = null;
|
||||
for (Channel ch : thing.getChannels()) {
|
||||
if (ch.getUID().getAsString().endsWith(stateData.capabilityAttribute)) {
|
||||
matchingChannel = ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matchingChannel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SmartthingsConverter converter = converters.get(matchingChannel.getUID());
|
||||
|
||||
// If value from Smartthings is null then stop here
|
||||
State state;
|
||||
if (stateData.value != null) {
|
||||
state = converter.convertToOpenHab(matchingChannel.getAcceptedItemType(), stateData);
|
||||
} else {
|
||||
state = UnDefType.NULL;
|
||||
}
|
||||
|
||||
updateState(matchingChannel.getUID(), state);
|
||||
logger.trace("Smartthings updated State for channel: {} to {}", matchingChannel.getUID().getAsString(),
|
||||
state.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getThing().getConfiguration().as(SmartthingsThingConfig.class);
|
||||
if (!validateConfig(config)) {
|
||||
return;
|
||||
}
|
||||
smartthingsName = config.smartthingsName;
|
||||
timeout = config.smartthingsTimeout;
|
||||
|
||||
// Create converters for each channel
|
||||
for (Channel ch : thing.getChannels()) {
|
||||
@Nullable
|
||||
String converterName = ch.getProperties().get(smartthingsConverterName);
|
||||
// Will be null if no explicit converter was specified
|
||||
if (converterName == null || converterName.isEmpty()) {
|
||||
// A converter was Not specified so use the channel id
|
||||
converterName = ch.getUID().getId();
|
||||
}
|
||||
|
||||
// Try to get the converter
|
||||
SmartthingsConverter cvtr = getConverter(converterName);
|
||||
if (cvtr == null) {
|
||||
// If there is no channel specific converter the get the "default" converter
|
||||
cvtr = getConverter("default");
|
||||
}
|
||||
|
||||
if (cvtr != null) {
|
||||
// cvtr should never be null because there should always be a "default" converter
|
||||
converters.put(ch.getUID(), cvtr);
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
private @Nullable SmartthingsConverter getConverter(String converterName) {
|
||||
// Converter name will be a name such as "switch" which has to be converted into the full class name such as
|
||||
// org.openhab.binding.smartthings.internal.converter.SmartthingsSwitchConveter
|
||||
StringBuffer converterClassName = new StringBuffer(
|
||||
"org.openhab.binding.smartthings.internal.converter.Smartthings");
|
||||
converterClassName.append(Character.toUpperCase(converterName.charAt(0)));
|
||||
converterClassName.append(converterName.substring(1));
|
||||
converterClassName.append("Converter");
|
||||
try {
|
||||
Constructor<?> constr = Class.forName(converterClassName.toString()).getDeclaredConstructor(Thing.class);
|
||||
constr.setAccessible(true);
|
||||
SmartthingsConverter cvtr = (SmartthingsConverter) constr.newInstance(thing);
|
||||
return cvtr;
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Most of the time there is no channel specific converter, the default converter is all that is needed.
|
||||
logger.trace("No Custom converter exists for {} ({})", converterName, converterClassName);
|
||||
} catch (NoSuchMethodException e) {
|
||||
logger.warn("NoSuchMethodException occurred for {} ({}) {}", converterName, converterClassName,
|
||||
e.getMessage());
|
||||
} catch (InvocationTargetException e) {
|
||||
logger.warn("InvocationTargetException occurred for {} ({}) {}", converterName, converterClassName,
|
||||
e.getMessage());
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.warn("IllegalAccessException occurred for {} ({}) {}", converterName, converterClassName,
|
||||
e.getMessage());
|
||||
} catch (InstantiationException e) {
|
||||
logger.warn("InstantiationException occurred for {} ({}) {}", converterName, converterClassName,
|
||||
e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean validateConfig(SmartthingsThingConfig config) {
|
||||
String name = config.smartthingsName;
|
||||
if (name.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Smartthings device name is missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigStatusMessage> getConfigStatus() {
|
||||
Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<ConfigStatusMessage>();
|
||||
|
||||
// The name must be provided
|
||||
String stName = config.smartthingsName;
|
||||
if (stName.isEmpty()) {
|
||||
configStatusMessages.add(ConfigStatusMessage.Builder.error(SmartthingsBindingConstants.SMARTTHINGS_NAME)
|
||||
.withMessageKeySuffix(SmartthingsThingConfigStatusMessage.SMARTTHINGS_NAME_MISSING)
|
||||
.withArguments(SmartthingsBindingConstants.SMARTTHINGS_NAME).build());
|
||||
}
|
||||
|
||||
return configStatusMessages;
|
||||
}
|
||||
|
||||
public String getSmartthingsName() {
|
||||
return smartthingsName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("smartthingsName :").append(smartthingsName);
|
||||
sb.append(", thing UID: ").append(this.thing.getUID());
|
||||
sb.append(", thing label: ").append(this.thing.getLabel());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="smartthings" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Samsung Smartthings Binding</name>
|
||||
<description>This is the binding for the Samsung Smartthings hub.</description>
|
||||
<author>Bob Raker</author>
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
|
||||
https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:smartthings:thing-type-parameters">
|
||||
<parameter name="smartthingsName" type="text" required="true">
|
||||
<label>Smartthings Name</label>
|
||||
<description>User assigned Smartthings device name</description>
|
||||
</parameter>
|
||||
<parameter name="smartthingsLocation" type="text">
|
||||
<label>Smartthings Location</label>
|
||||
<description>Where the device is located</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,7 @@
|
||||
# Config status messages
|
||||
config-status.error.missing-ip-configuration=No IP address for the Smartthings bridge has been provided.
|
||||
config-status.error.missing-port-configuration=No port for the Smartthings bridge has been provided.
|
||||
config-status.error.missing-smartthings-name=No Smartthings name has been provided
|
||||
|
||||
binding.smartthings.name = Smartthings
|
||||
binding.smartthings.description = Samsung Smartthings hub
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user