added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.smartthings</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ds.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,130 @@
# Samsung Smartthings Binding
This binding integrates the Samsung Smartthings Hub into openHAB. This is implemented as an openHAB 2 binding.
## Supported things
This binding supports most of the Smartthings devices that are defined in the [Smartthings Capabilities list](http://docs.smartthings.com/en/latest/capabilities-reference.html). If you find a device that doesn't work [follow these instructions](doc/Troubleshooting.md) to collect the required data so it can be added in a future release.
## Discovery
Discovery allows openHAB to examine a binding and automatically find the Things available on that binding.
Discovery is supported by the Smartthings binding and is run automatically on startup.
## Smartthings Configuration
Prior to running the binding the Smartthings hub must have the required openHAB software installed. [Follow these instructions](doc/SmartthingsInstallation.md)
**The binding will not work until this part has been completed, do not skip this part of the setup.**
## openHAB Configuration
This binding is an openHAB binding and uses the Bridge / Thing design with the Smartthings Hub being the Bridge and the controlled modules being the Things. The following definitions are specified in the .things file.
### Bridge Configuration
The bridge requires the IP address and port used to connect the openHAB server to the Smartthings Hub.
Bridge smartthings:smartthings:Home [ smartthingsIp="192.168.1.12", smartthingsPort=39500 ] {
where:
* **smartthings:smartthings:Home** identifies this is a smartthings hub named Home. The first two segments must be smartthings:smartthings. You can choose any unique name for the the last segment. The last segment is used when you identify items connected to this hubthingTypeId.
* **smartthingsIp** is the IP address of the Smartthings Hub. Your router should be configured such that the Smartthings Hub is always assigned to this IP address.
* **smartthingsPort** is the port the Smartthings hub listens on. 39500 is the port assigned by Smartthings so it should be used unless you have a good reason for using another port.
**Warning** This binding only supports one Bridge. If you try to configure a second bridge it will be ignored.
### Thing Configuration
Each attached thing must specify the type of device and it's Smartthings device name. The format of the Thing description is:
Thing <thingTypeId> name [ smartthingsName="<deviceName>", {smartthingsTimeout=<timeout>} ]
where:
* **[thingTypeId](http://docs.smartthings.com/en/latest/capabilities-reference.html)** corresponds to the "Preferences Reference" in the Smartthings Capabilities document but without the capability. prefix. i.e. A dimmer switch in the Capabilities document has a Preferences reference of capability.switchLevel, therefore the &lt;thingTypeId&gt; is switchLevel.
* **name** is what you want to call this thing and is used in defining the items that use this thing.
* **deviceName** is the name you assigned to the device when you discovered and connected to it in the Smartthings App
* Optional: **timeout** is how long openHAB will wait for a response to the request before throwing a timeout exception. The default is 3 seconds.
**Example**
Bridge smartthings:smartthings:Home [ smartthingsIp="192.168.1.12", smartthingsPort=39500 ] {
Thing switchLevel KitchenLights [ smartthingsName="Kitchen lights" ]
Thing contactSensor MainGarageDoor [ smartthingsName="Garage Door Open Sensor" ]
Thing temperatureMeasurement MainGarageTemp [ smartthingsName="Garage Door Open Sensor" ]
Thing battery MainGarageBattery [ smartthingsName="Garage Door Open Sensor" ]
Thing switch OfficeLight [ smartthingsName="Office Light", smartthingsTimeout=7 ]
Thing valve SimulatedValve [ smartthingsName="Simulated Valve" ]
}
## Items
These are specified in the .items file. This section describes the specifics related to this binding. Please see the [Items documentation](https://www.openhab.org/docs/configuration/items.html) for a full explanation of configuring items.
The most important thing is getting the **channel** specification correct. The general format is:
{ channel="smartthings:<thingTypeId>:<hubName>:<thingName>:<channelId>" }
The parts (separated by :) are defined as:
1. **smartthings** to specify this is a smartthings device
2. **thingTypeId** specifies the type of the thing you are connecting to. This is the same as described in the last section.
3. **hubName** identifies the name of the hub specified above. This corresponds to the third segment in the **Bridge** definition.
4. **thingName** identifes the thing this is attached to and is the "name" you specified in the **Thing** definition.
5. **channelId** corresponds the the attribute in the [Smartthings Capabilities list](http://docs.smartthings.com/en/latest/capabilities-reference.html). For switch it would be "switch".
**Example**
Dimmer KitchenLights "Kitchen lights level" <slider> { channel="smartthings:switchLevel:Home:KitchenLights:level" }
Switch KitchenLightSwitch "Kitchen lights switch" <light> { channel="smartthings:switchLevel:Home:KitchenLights:switch" }
Contact MainGarageDoor "Garage door status [%s]" <garagedoor> { channel="smartthings:contactSensor:Home:MainGarageDoor:contact" }
Number MainGarageTemp "Garage temperature [%.0f]" <temperature> { channel="smartthings:temperatureMeasurement:Home:MainGarageTemp:temperature" }
Number MainGarageBattery "Garage battery [%.0f]" <battery> { channel="smartthings:battery:Home:MainGarageBattery:battery" }
Switch OfficeLight "Office light" <light> { channel="smartthings:switch:Home:OfficeLight:switch" }
String SimulatedValve "Simulated valve" { channel="smartthings:valve:Home:SimulatedValve:valve" }
**Special note about Valves**
Smarttings includes a **valve** which can be Open or Closed but openHAB does not include a Valve item type. Therefore, the valve is defined as a having an item type of String. And, therefore the item needs to be defined with an item type of string. It can be controlled in the sitemap by specifying the Element type of Switch and providing a mapping of: mappings=[open="Open", closed="Close"]. Such as:
Switch item=SimulatedValve mappings=[open="Open", closed="Close"]
**RGB Bulb example**
Here is a sample configuration for a RGB bulb, such as a Sengled model E11-N1EA bulb. Currently this binding does not have a RGB specific bulb therefore a Thing is required for each part of the bulb.
**Example**
**things file**
colorControl SengledColorControl [ smartthingsName="Sengled Bulb"]
colorTemperature SengledColorTemperature [ smartthingsName="Sengled Bulb"]
switch SengledSwitch [ smartthingsName="Sengled Bulb"]
switchLevel SengledSwitchLevel [ smartthingsName="Sengled Bulb"]
**items file**
Color SengledColorControl "Sengled bulb color" <colorpicker> {channel="smartthings:colorControl:Home:SengledColorControl:color"}
Number SengledTemperature "Sengled bulb color temperature" {channel="smartthings:colorTemperature:Home:SengledColorTemperature:colorTemperature"}
Switch SengledSwitch "Sengled bulb switch" <switch> {channel="smartthings:switch:Home:SengledSwitch:switch"}
Dimmer SengledDimmer "Sengled bulb dimmer" <slider> {channel="smartthings:switchLevel:Home:SengledSwitchLevel:level"}
**sitemap file**
Frame label="Sengled RGBW Bulb" {
Switch item=SengledSwitch label="Switch"
Slider item=SengledDimmer label="Level [%d]"
Text item=SengledTemperature label="Color Temperature [%d]"
Colorpicker item=SengledColorControl label="Color [%s]" icon="colorwheel"
}
## References
1. [openHAB configuration documentation](http://docs.openhab.org/configuration/index.html)
2. [Smartthings Capabilities Reference](http://docs.smartthings.com/en/latest/capabilities-reference.html)
3. [Smartthings Developers Documentation](http://docs.smartthings.com/en/latest/index.html)
4. [Smartthings Development Environment](https://graph.api.smartthings.com/)

View File

@@ -0,0 +1,130 @@
/**
* OpenHAB Bridge
*
* Authors
* - st.john.johnson@gmail.com
* - jeremiah.wuenschel@gmail.com
* - rjraker@gmail.com - 1/30/17 - modified to work with OpenHAB
*
* Copyright 2016 - 2018
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*/
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
metadata {
definition (name: "OpenHabDeviceHandler", namespace: "bobrak", author: "St. John Johnson, Jeremiah Wuenschel and Bob Raker") {
capability "Notification"
}
preferences {
input("ip", "string",
title: "OpenHAB IP Address",
description: "OpenHAB IP Address",
required: true,
displayDuringSetup: true
)
input("port", "string",
title: "OpenHAB Port",
description: "OpenHAB Port",
required: true,
displayDuringSetup: true
)
input("mac", "string",
title: "OpenHAB MAC Address",
description: "MAC Address of OpenHAB server",
required: true,
displayDuringSetup: true
)
}
simulator {}
tiles {
valueTile("basic", "device.ip", width: 3, height: 2) {
state("basic", label:'OK')
}
main "basic"
}
}
// Store the MAC address as the device ID so that it can talk to SmartThings
def setNetworkAddress() {
// Setting Network Device Id
def hex = "$settings.mac".toUpperCase().replaceAll(':', '')
if (device.deviceNetworkId != "$hex") {
device.deviceNetworkId = "$hex"
log.debug "Device Network Id set to ${device.deviceNetworkId}"
}
}
def installed() {
def ip = device.hub.getDataValue("localIP")
def port = device.hub.getDataValue("localSrvPortTCP")
log.debug "HTTP Bridge device handler installed. Listening on ${ip} + ":" + ${port}"
}
// Parse events from OpenHAB
def parse(String description) {
def startTime = now()
setNetworkAddress()
def msg = parseLanMessage(description)
log.debug "Msg '${msg}'"
if (msg.header.contains(' /update ')) {
msg.data.path = "update"
} else if (msg.header.contains(' /state ')) {
msg.data.path = "state"
} else if (msg.header.contains(' /discovery ')) {
msg.data = [path: "discovery"]
} else {
if ( msg.status == 200 ) {
// This would be a response from OpenHAB to the last message - it return 200 since there is nothing to do
return
}
log.error "received a request with an unknown path: ${msg.header}"
return
}
log.debug "Creating event with message: ${msg.data}"
// Setting parameter isStateChange to true causes the event to be propagated even if the state has not changed.
//return createEvent(name: 'message', value: new JsonOutput().toJson(msg.data), isStateChange: true)
return createEvent(name: 'message', value: new JsonOutput().toJson(msg.data))
}
// Send message to OpenHAB
def deviceNotification(message) {
if (device.hub == null) {
log.error "Hub is null, must set the hub in the device settings so we can get local hub IP and port"
return
}
log.debug "Sending '${message}' to ${ip}:${port} with mac: ${mac}"
setNetworkAddress()
def slurper = new JsonSlurper()
def parsed = slurper.parseText(message)
def headers = [:]
headers.put("HOST", "$ip:$port")
headers.put("Content-Type", "application/json")
def hubAction = new physicalgraph.device.HubAction(
method: "POST",
path: parsed.path,
headers: headers,
body: parsed.body
)
hubAction
}

View File

@@ -0,0 +1,76 @@
# Installation of Smartthings code
To use the Smartthings, openHAB binding code needs to be installed on the Smartthings Hub. Currently the Smartthings code is bundled with the binding.
## Installation of artifacts on the Smartthings HUB
The following steps need to be done on the Smartthings hub using the web based [Smartthings developers tools](https://graph.api.smartthings.com/).
### Initial steps
These steps assume you already have a Smartthings Hub and have set it up. And, you have created an account.
1. Open the developers website using the link above.
2. Logon using the same email and password as on your Smartthings phone app.
3. Click on locations
4. Verify your hub is listed.
### Copying Smartthings files
The files are located in the GitHub [repository](https://github.com/openhab/openhab2-addons/tree/master/addons/binding/org.openhab.binding.smartthings/contrib).
The following files need to be deployed
* OpenHabAppV2 - This is a SmartApp that receives requests from openHAB and returns the needed data
* OpenHabDeviceHandler - This is a lower level module that provides a connection between openHAB and the Hub using the LAN connection
### Install OpenHabAppV2
1. Locate OpenHabAppV2.groovy in the /contrib/SmartApps Directory.
2. Open OpenHabAppV2.groovy in an editor (Some program you can use to copy the contents to the clipboard)
3. Copy the contents to the clipboard
4. Using the Smartthings developers tools:
5. Logon, if you are not logged on
6. Select **My SmartApps**
7. Click on the **+ New SmartApp** near the top right
8. Click on the **From Code** tab
9. Paste the contents of the clipboard
10. Click on the **Create** button near the bottom left
11. Click on **Publish -> For Me**
12. The SmartApp is now ready
### Install OpenHabDeviceHandler
1. Locate OpenHabDeviceHandler.groovy in the /contrib/DeviceHandlers Directory.
2. Open OpenHabDeviceHandler.groovy in an editor (Some program you can use to copy the contents to the clipboard)
3. Copy the contents to the clipboard
4. Using the Smartthings developers tools:
5. Select **My Device Handlers**
6. Click on the **+ Create New Device Handler** near the top right
7. Click on the **From Code** tab
8. Paste the contents of the clipboard
9. Click on the **Create** button near the bottom left
10. Click on **Publish -> For Me**
11. The Device Handler is now ready
### Create the Device
1. Using the Smartthings developers tools:
2. Select **My Devices**
3. Click on the **+ New Device** near the top right
4. Enter the following data in the form:
* Name: OpenHabDevice
* Label: OpenHabDevice
* Device Network ID: This needs to be the MAC address of your OpenHAB server with no spaces or punctuation
* Type: OpenHabDeviceHandler (This should be the last one on the list)
* Location: (Select from the dropdown)
* Hub: (Select from the dropdown)
5. Click on the **Create** button near the bottom left
6. In the Preferences section enter the following:
* ip: (This is the IP address of your openHAB server)
* mac: (This is the same as the Device Network ID but with : between segments
* port: 8080 (This is the port of the openHAB application on your server)
* Save the preferences
## Configuration in the Smartthings App
Next the App needs to be configured using the Smartthings App on your smartphone. These instructions are for the new app.
1. Start the Smartthings App on your phone
2. Select the menu (3 horizontal bars) in the upper left corner
3. Select **SmartApps**
4. Click the **+** (Add) in the upper right
5. Scroll to the bottom and select **OpenHabAppV2**
* In the selection screen select the devices you want to interact with openHAB. **Warning** devices not enabled (lacking the check mark in the box for the specific device) will be **ignored** by openHAB.
* Near the bottom of the screen is **Notify this virtual device**, click on it and select **OpenHabDevice**.
* Finally click **Done** at the bottom of the screen.

View File

@@ -0,0 +1,42 @@
# Smartthings Binding Troubleshooting Guidelines
Below are some recommendations on resolving issues with things not working as expected
## Device specific issues
If the binding is working for some devices but there is one device that doesn't seem to work then verify that the device is supported by checking the following in the PaperUI:
1. Open the PaperUI webpage
2. Select Configuration -> Bindings -> Smartthings
3. Verify that the thing you want to use is included in the list of Supported Things
If the device is listed then create a new topic in the [openHAB Community Add-ons -> Bindings](https://community.openhab.org/c/add-ons/bindings/) website.
## Setting openHAB logs to Debug
You will need to edit the logging configuration file and set the log level for the Smartthings binding to debug.
The following assumes you are running on Linux or Raspbian
Follow these steps on your openHAB server:
1. If logged on to the server: cd /var/lib/openhab2/etc or if using Samba open file explorer to \\\\OPENHABIANPI\openHAB-userdata\etc
2. Edit the file org.ops4j.pax.logging.cfg
* Just ** before ** the line "log4j2.logger.openhab.name = org.openhab" add the following lines:
* log4j2.logger.smartthings.name=org.openhab.binding.smartthings
* log4j2.logger.smartthings.level=DEBUG
* Save the file
3. Restart the server (i.e. sudo systemctl restart openhab2.service)
## Viewing the openHAB logs
Viewing the logs is best done on the openHAB server where you can using the linux command "tail -f" to watch the log messages as they are created
Follow these steps on your openHAB server:
1. cd /var/log/openhab2 or if using Samba open file explorer to \\OPENHABIANPI\openHAB-log
2. The log file you want to see is openhab.log
3. Using unix "tail -f openhab.log"
4. Try the device that isn't working and look for log messages related to the device you are using
## Viewing Smartthings logs
On the Smartthings hub all of the incoming messages and the responses are logged. Looking at these logs can be very informative.
To view these logs perform the following steps:
1. Using the Smartthings developers [IDE](https://graph.api.smartthings.com/):
2. Logon, if you are not logged on
3. Select **Live Logging** from the top menu bar.
4. Try the device that isn't working and look for log messages related to the device you are having troubles with

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.smartthings</artifactId>
<name>openHAB Add-ons :: Bundles :: Samsung Smartthings Binding</name>
</project>

View File

@@ -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>

View File

@@ -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";
}

View File

@@ -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;
}
}

View File

@@ -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");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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";
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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";
}

View File

@@ -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();
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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