Compare commits
1 Commits
staging
...
MSA-2524-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf0e5ffc31 |
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* 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}"
|
||||
}
|
||||
}
|
||||
|
||||
// Parse events from OpenHAB
|
||||
def parse(String description) {
|
||||
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 == 204 ) {
|
||||
// This would be a response from OpenHAB to the last message - it return 204 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 propigated even if the state has not changed.
|
||||
return createEvent(name: 'message', value: new JsonOutput().toJson(msg.data), isStateChange: true)
|
||||
}
|
||||
|
||||
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}"
|
||||
}
|
||||
|
||||
// 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 device"
|
||||
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
|
||||
}
|
||||
108
devicetypes/stj/mqtt-bridge.src/mqtt-bridge.groovy
Normal file
108
devicetypes/stj/mqtt-bridge.src/mqtt-bridge.groovy
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* MQTT Bridge
|
||||
*
|
||||
* Authors
|
||||
* - st.john.johnson@gmail.com
|
||||
* - jeremiah.wuenschel@gmail.com
|
||||
*
|
||||
* Copyright 2016
|
||||
*
|
||||
* 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: "MQTT Bridge", namespace: "stj", author: "St. John Johnson and Jeremiah Wuenschel") {
|
||||
capability "Notification"
|
||||
}
|
||||
|
||||
preferences {
|
||||
input("ip", "string",
|
||||
title: "MQTT Bridge IP Address",
|
||||
description: "MQTT Bridge IP Address",
|
||||
required: true,
|
||||
displayDuringSetup: true
|
||||
)
|
||||
input("port", "string",
|
||||
title: "MQTT Bridge Port",
|
||||
description: "MQTT Bridge Port",
|
||||
required: true,
|
||||
displayDuringSetup: true
|
||||
)
|
||||
input("mac", "string",
|
||||
title: "MQTT Bridge MAC Address",
|
||||
description: "MQTT Bridge MAC Address",
|
||||
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}"
|
||||
}
|
||||
}
|
||||
|
||||
// Parse events from the Bridge
|
||||
def parse(String description) {
|
||||
setNetworkAddress()
|
||||
|
||||
log.debug "Parsing '${description}'"
|
||||
def msg = parseLanMessage(description)
|
||||
|
||||
return createEvent(name: "message", value: new JsonOutput().toJson(msg.data))
|
||||
}
|
||||
|
||||
// Send message to the Bridge
|
||||
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 device"
|
||||
setNetworkAddress()
|
||||
|
||||
def slurper = new JsonSlurper()
|
||||
def parsed = slurper.parseText(message)
|
||||
|
||||
if (parsed.path == '/subscribe') {
|
||||
parsed.body.callback = device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
862
smartapps/bobrak/openhabappv2.src/openhabappv2.groovy
Normal file
862
smartapps/bobrak/openhabappv2.src/openhabappv2.groovy
Normal file
@@ -0,0 +1,862 @@
|
||||
/**
|
||||
* OpenHabAppV2
|
||||
*
|
||||
* Description
|
||||
* Provides two way communications between a Smartthings Hub and OpenHAB
|
||||
* Messages from OpenHAB with the following paths are supported and perform the following functions
|
||||
* /state - returns the state of the specified device and attribute, i.e. on, off, 95
|
||||
* /update - Updates the state of the specified device and attribute
|
||||
* /discovery - Returns a list of the devices
|
||||
* /error - Returns error messages to OpenHAB for logging
|
||||
* Messages are sent to OpenHAB with the following paths
|
||||
* /smartthings/push - When an event occurs on a monitored device the new value is sent to OpenHAB
|
||||
*
|
||||
* Authors
|
||||
* - rjraker@gmail.com - 1/30/17 -
|
||||
* - st.john.johnson@gmail.com and jeremiah.wuenschel@gmail.com- original code for interface with another device
|
||||
*
|
||||
* Copyright 2016, 2017
|
||||
*
|
||||
* 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
|
||||
import groovy.transform.Field
|
||||
|
||||
// Massive lookup tree
|
||||
@Field CAPABILITY_MAP = [
|
||||
"accelerationSensor": [
|
||||
name: "Acceleration Sensor",
|
||||
capability: "capability.accelerationSensor",
|
||||
attributes: [
|
||||
"acceleration"
|
||||
]
|
||||
],
|
||||
"alarm": [
|
||||
name: "Alarm",
|
||||
capability: "capability.alarm",
|
||||
attributes: [
|
||||
"alarm"
|
||||
],
|
||||
action: "actionAlarm"
|
||||
],
|
||||
"battery": [
|
||||
name: "Battery",
|
||||
capability: "capability.battery",
|
||||
attributes: [
|
||||
"battery"
|
||||
]
|
||||
],
|
||||
"beacon": [
|
||||
name: "Beacon",
|
||||
capability: "capability.beacon",
|
||||
attributes: [
|
||||
"presence"
|
||||
]
|
||||
],
|
||||
"bulb": [
|
||||
name: "Bulb",
|
||||
capability: "capability.bulb",
|
||||
attributes: [
|
||||
"switch"
|
||||
],
|
||||
action: "actionOnOff"
|
||||
],
|
||||
"button": [
|
||||
name: "Button",
|
||||
capability: "capability.button",
|
||||
attributes: [
|
||||
"button"
|
||||
]
|
||||
],
|
||||
"carbonDioxideMeasurement": [
|
||||
name: "Carbon Dioxide Measurement",
|
||||
capability: "capability.carbonDioxideMeasurement",
|
||||
attributes: [
|
||||
"carbonDioxide"
|
||||
]
|
||||
],
|
||||
"carbonMonoxideDetector": [
|
||||
name: "Carbon Monoxide Detector",
|
||||
capability: "capability.carbonMonoxideDetector",
|
||||
attributes: [
|
||||
"carbonMonoxide"
|
||||
]
|
||||
],
|
||||
"colorControl": [
|
||||
name: "Color Control",
|
||||
capability: "capability.colorControl",
|
||||
attributes: [
|
||||
"hue",
|
||||
"saturation",
|
||||
"color"
|
||||
],
|
||||
action: "actionColor"
|
||||
],
|
||||
"colorTemperature": [
|
||||
name: "Color Temperature",
|
||||
capability: "capability.colorTemperature",
|
||||
attributes: [
|
||||
"colorTemperature"
|
||||
],
|
||||
action: "actionColorTemperature"
|
||||
],
|
||||
"consumable": [
|
||||
name: "Consumable",
|
||||
capability: "capability.consumable",
|
||||
attributes: [
|
||||
"consumable"
|
||||
],
|
||||
action: "actionConsumable"
|
||||
],
|
||||
"contactSensor": [
|
||||
name: "Contact Sensor",
|
||||
capability: "capability.contactSensor",
|
||||
attributes: [
|
||||
"contact"
|
||||
]
|
||||
],
|
||||
"doorControl": [
|
||||
name: "Door Control",
|
||||
capability: "capability.doorControl",
|
||||
attributes: [
|
||||
"door"
|
||||
],
|
||||
action: "actionOpenClosed"
|
||||
],
|
||||
"energyMeter": [
|
||||
name: "Energy Meter",
|
||||
capability: "capability.energyMeter",
|
||||
attributes: [
|
||||
"energy"
|
||||
]
|
||||
],
|
||||
"estimatedTimeOfArrival": [
|
||||
name: "Estimated Time Of Arrival",
|
||||
capability: "capability.estimatedTimeOfArrival",
|
||||
attributes: [
|
||||
"eta"
|
||||
]
|
||||
],
|
||||
"garageDoorControl": [
|
||||
name: "Garage Door Control",
|
||||
capability: "capability.garageDoorControl",
|
||||
attributes: [
|
||||
"door"
|
||||
],
|
||||
action: "actionOpenClosed"
|
||||
],
|
||||
"holdableButton": [
|
||||
name: "Holdable Button",
|
||||
capability: "capability.holdableButton",
|
||||
attributes: [
|
||||
"button",
|
||||
"numberOfButtons"
|
||||
],
|
||||
action: "actionOpenClosed"
|
||||
],
|
||||
"illuminanceMeasurement": [
|
||||
name: "Illuminance Measurement",
|
||||
capability: "capability.illuminanceMeasurement",
|
||||
attributes: [
|
||||
"illuminance"
|
||||
]
|
||||
],
|
||||
"imageCapture": [
|
||||
name: "Image Capture",
|
||||
capability: "capability.imageCapture",
|
||||
attributes: [
|
||||
"image"
|
||||
]
|
||||
],
|
||||
"indicator": [
|
||||
name: "Indicator",
|
||||
capability: "capability.indicator",
|
||||
attributes: [
|
||||
"indicatorStatus"
|
||||
],
|
||||
action: indicator
|
||||
],
|
||||
"infraredLevel": [
|
||||
name: "Infrared Level",
|
||||
capability: "capability.infraredLevel",
|
||||
attributes: [
|
||||
"infraredLevel"
|
||||
],
|
||||
action: "actionLevel"
|
||||
],
|
||||
"lock": [
|
||||
name: "Lock",
|
||||
capability: "capability.lock",
|
||||
attributes: [
|
||||
"lock"
|
||||
],
|
||||
action: "actionLock"
|
||||
],
|
||||
"lockOnly": [
|
||||
name: "Lock Only",
|
||||
capability: "capability.lockOnly",
|
||||
attributes: [
|
||||
"lock"
|
||||
],
|
||||
action: "actionLock"
|
||||
],
|
||||
"mediaController": [
|
||||
name: "Media Controller",
|
||||
capability: "capability.mediaController",
|
||||
attributes: [
|
||||
"activities",
|
||||
"currentActivity"
|
||||
]
|
||||
],
|
||||
"motionSensor": [
|
||||
name: "Motion Sensor",
|
||||
capability: "capability.motionSensor",
|
||||
attributes: [
|
||||
"motion"
|
||||
],
|
||||
action: "actionActiveInactive"
|
||||
],
|
||||
"musicPlayer": [
|
||||
name: "Music Player",
|
||||
capability: "capability.musicPlayer",
|
||||
attributes: [
|
||||
"status",
|
||||
"level",
|
||||
"trackDescription",
|
||||
"trackData",
|
||||
"mute"
|
||||
],
|
||||
action: "actionMusicPlayer"
|
||||
],
|
||||
"outlet": [
|
||||
name: "Outlet",
|
||||
capability: "capability.outlet",
|
||||
attributes: [
|
||||
"switch"
|
||||
],
|
||||
action: "actionOnOff"
|
||||
],
|
||||
"pHMeasurement": [
|
||||
name: "pH Measurement",
|
||||
capability: "capability.pHMeasurement",
|
||||
attributes: [
|
||||
"pH"
|
||||
]
|
||||
],
|
||||
"powerMeter": [
|
||||
name: "Power Meter",
|
||||
capability: "capability.powerMeter",
|
||||
attributes: [
|
||||
"power"
|
||||
]
|
||||
],
|
||||
"powerSource": [
|
||||
name: "Power Source",
|
||||
capability: "capability.powerSource",
|
||||
attributes: [
|
||||
"powerSource"
|
||||
]
|
||||
],
|
||||
"presenceSensor": [
|
||||
name: "Presence Sensor",
|
||||
capability: "capability.presenceSensor",
|
||||
attributes: [
|
||||
"presence"
|
||||
]
|
||||
],
|
||||
"relativeHumidityMeasurement": [
|
||||
name: "Relative Humidity Measurement",
|
||||
capability: "capability.relativeHumidityMeasurement",
|
||||
attributes: [
|
||||
"humidity"
|
||||
]
|
||||
],
|
||||
"relaySwitch": [
|
||||
name: "Relay Switch",
|
||||
capability: "capability.relaySwitch",
|
||||
attributes: [
|
||||
"switch"
|
||||
],
|
||||
action: "actionOnOff"
|
||||
],
|
||||
"shockSensor": [
|
||||
name: "Shock Sensor",
|
||||
capability: "capability.shockSensor",
|
||||
attributes: [
|
||||
"shock"
|
||||
]
|
||||
],
|
||||
"signalStrength": [
|
||||
name: "Signal Strength",
|
||||
capability: "capability.signalStrength",
|
||||
attributes: [
|
||||
"lqi",
|
||||
"rssi"
|
||||
]
|
||||
],
|
||||
"sleepSensor": [
|
||||
name: "Sleep Sensor",
|
||||
capability: "capability.sleepSensor",
|
||||
attributes: [
|
||||
"sleeping"
|
||||
]
|
||||
],
|
||||
"smokeDetector": [
|
||||
name: "Smoke Detector",
|
||||
capability: "capability.smokeDetector",
|
||||
attributes: [
|
||||
"smoke",
|
||||
"carbonMonoxide"
|
||||
]
|
||||
],
|
||||
"soundPressureLevel": [
|
||||
name: "Sound Pressure Level",
|
||||
capability: "capability.soundPressureLevel",
|
||||
attributes: [
|
||||
"soundPressureLevel"
|
||||
]
|
||||
],
|
||||
"soundSensor": [
|
||||
name: "Sound Sensor",
|
||||
capability: "capability.soundSensor",
|
||||
attributes: [
|
||||
"phraseSpoken"
|
||||
]
|
||||
],
|
||||
"speechRecognition": [
|
||||
name: "Speech Recognition",
|
||||
capability: "capability.speechRecognition",
|
||||
action: [
|
||||
"speak"
|
||||
]
|
||||
],
|
||||
"stepSensor": [
|
||||
name: "Step Sensor",
|
||||
capability: "capability.stepSensor",
|
||||
attributes: [
|
||||
"steps",
|
||||
"goal"
|
||||
]
|
||||
],
|
||||
"switch": [
|
||||
name: "Switch",
|
||||
capability: "capability.switch",
|
||||
attributes: [
|
||||
"switch"
|
||||
],
|
||||
action: "actionOnOff"
|
||||
],
|
||||
"switchLevel": [
|
||||
name: "Dimmer Switch",
|
||||
capability: "capability.switchLevel",
|
||||
attributes: [
|
||||
"level"
|
||||
],
|
||||
action: "actionLevel"
|
||||
],
|
||||
"soundPressureLevel": [
|
||||
name: "Sound Pressure Level",
|
||||
capability: "capability.soundPressureLevel",
|
||||
attributes: [
|
||||
"soundPressureLevel"
|
||||
]
|
||||
],
|
||||
"tamperAlert": [
|
||||
name: "Tamper Alert",
|
||||
capability: "capability.tamperAlert",
|
||||
attributes: [
|
||||
"tamper"
|
||||
]
|
||||
],
|
||||
"temperatureMeasurement": [
|
||||
name: "Temperature Measurement",
|
||||
capability: "capability.temperatureMeasurement",
|
||||
attributes: [
|
||||
"temperature"
|
||||
]
|
||||
],
|
||||
"thermostat": [
|
||||
name: "Thermostat",
|
||||
capability: "capability.thermostat",
|
||||
attributes: [
|
||||
"temperature",
|
||||
"heatingSetpoint",
|
||||
"coolingSetpoint",
|
||||
"thermostatSetpoint",
|
||||
"thermostatMode",
|
||||
"thermostatFanMode",
|
||||
"thermostatOperatingState"
|
||||
],
|
||||
action: "actionThermostat"
|
||||
],
|
||||
"thermostatCoolingSetpoint": [
|
||||
name: "Thermostat Cooling Setpoint",
|
||||
capability: "capability.thermostatCoolingSetpoint",
|
||||
attributes: [
|
||||
"coolingSetpoint"
|
||||
],
|
||||
action: "actionCoolingThermostat"
|
||||
],
|
||||
"thermostatFanMode": [
|
||||
name: "Thermostat Fan Mode",
|
||||
capability: "capability.thermostatFanMode",
|
||||
attributes: [
|
||||
"thermostatFanMode"
|
||||
],
|
||||
action: "actionThermostatFan"
|
||||
],
|
||||
"thermostatHeatingSetpoint": [
|
||||
name: "Thermostat Heating Setpoint",
|
||||
capability: "capability.thermostatHeatingSetpoint",
|
||||
attributes: [
|
||||
"heatingSetpoint"
|
||||
],
|
||||
action: "actionHeatingThermostat"
|
||||
],
|
||||
"thermostatMode": [
|
||||
name: "Thermostat Mode",
|
||||
capability: "capability.thermostatMode",
|
||||
attributes: [
|
||||
"thermostatMode"
|
||||
],
|
||||
action: "actionThermostatMode"
|
||||
],
|
||||
"thermostatOperatingState": [
|
||||
name: "Thermostat Operating State",
|
||||
capability: "capability.thermostatOperatingState",
|
||||
attributes: [
|
||||
"thermostatOperatingState"
|
||||
]
|
||||
],
|
||||
"thermostatSetpoint": [
|
||||
name: "Thermostat Setpoint",
|
||||
capability: "capability.thermostatSetpoint",
|
||||
attributes: [
|
||||
"thermostatSetpoint"
|
||||
]
|
||||
],
|
||||
"threeAxis": [
|
||||
name: "Three Axis",
|
||||
capability: "capability.threeAxis",
|
||||
attributes: [
|
||||
"threeAxis"
|
||||
]
|
||||
],
|
||||
"timedSession": [
|
||||
name: "Timed Session",
|
||||
capability: "capability.timedSession",
|
||||
attributes: [
|
||||
"timeRemaining",
|
||||
"sessionStatus"
|
||||
],
|
||||
action: "actionTimedSession"
|
||||
],
|
||||
"touchSensor": [
|
||||
name: "Touch Sensor",
|
||||
capability: "capability.touchSensor",
|
||||
attributes: [
|
||||
"touch"
|
||||
]
|
||||
],
|
||||
"valve": [
|
||||
name: "Valve",
|
||||
capability: "capability.valve",
|
||||
attributes: [
|
||||
"valve"
|
||||
],
|
||||
action: "actionOpenClosed"
|
||||
],
|
||||
"voltageMeasurement": [
|
||||
name: "Voltage Measurement",
|
||||
capability: "capability.voltageMeasurement",
|
||||
attributes: [
|
||||
"voltage"
|
||||
]
|
||||
],
|
||||
"waterSensor": [
|
||||
name: "Water Sensor",
|
||||
capability: "capability.waterSensor",
|
||||
attributes: [
|
||||
"water"
|
||||
]
|
||||
],
|
||||
"windowShade": [
|
||||
name: "Window Shade",
|
||||
capability: "capability.windowShade",
|
||||
attributes: [
|
||||
"windowShade"
|
||||
],
|
||||
action: "actionOpenClosed"
|
||||
]
|
||||
]
|
||||
|
||||
definition(
|
||||
name: "OpenHabAppV2",
|
||||
namespace: "bobrak",
|
||||
author: "Bob Raker",
|
||||
description: "Provides two way communications between a Smartthings Hub and OpenHAB",
|
||||
category: "My Apps",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Connections/Cat-Connections.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Connections/Cat-Connections@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Connections/Cat-Connections@3x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
section("Send Notifications?") {
|
||||
input("recipients", "contact", title: "Send notifications to", multiple: true, required: false)
|
||||
}
|
||||
|
||||
section ("Input") {
|
||||
CAPABILITY_MAP.each { key, capability ->
|
||||
input key, capability["capability"], title: capability["name"], description: capability["key"], multiple: true, required: false
|
||||
}
|
||||
}
|
||||
|
||||
section ("Device") {
|
||||
input "openhabDevice", "capability.notification", title: "Notify this virtual device", required: true, multiple: false
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
// Unsubscribe from all events
|
||||
unsubscribe()
|
||||
// Subscribe to stuff
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
// Subscribe to new events from devices
|
||||
CAPABILITY_MAP.each { key, capability ->
|
||||
capability["attributes"].each { attribute ->
|
||||
if ( settings[key] != null ) {
|
||||
subscribe(settings[key], attribute, inputHandler)
|
||||
log.debug "Subscribing inputHandler to device \"${settings[key]}\" with attribute \"${attribute}\""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to events from the openhabDevice
|
||||
log.debug "Subscribing to event handler ${openHabDevice}"
|
||||
subscribe(openhabDevice, "message", openhabMessageHandler)
|
||||
}
|
||||
|
||||
// Receive an event from OpenHAB via the openhabDevice
|
||||
def openhabMessageHandler(evt) {
|
||||
def json = new JsonSlurper().parseText(evt.value)
|
||||
log.debug "Received device event from Message : ${json}"
|
||||
switch (json.path) {
|
||||
case "update":
|
||||
openhabUpdateHandler (evt)
|
||||
break
|
||||
case "state":
|
||||
openhabStateHandler (evt)
|
||||
break
|
||||
case "discovery":
|
||||
openhabDiscoveryHandler (evt)
|
||||
break
|
||||
default:
|
||||
log.debug "Received device event from Message **** UNEXPECTED **** : ${json}"
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for "current" state requests
|
||||
def openhabStateHandler(evt) {
|
||||
def json = new JsonSlurper().parseText(evt.value)
|
||||
log.debug "Received state event from openhabDevice: ${json}"
|
||||
|
||||
// Get the CAPABILITY_MAP entry for this device type
|
||||
def capability = CAPABILITY_MAP[json.capabilityKey]
|
||||
if (capability == null) {
|
||||
log.error "No capability: \"${json.capabilityKey}\" exists, make sure there is a CAPABILITY_MAP entry for this capability."
|
||||
def jsonOut = new JsonOutput().toJson([
|
||||
path: "/smartthings/error",
|
||||
body: [
|
||||
message: "Requested current state information for CAPABILITY: \"${json.capabilityKey}\" but this is not defined in the SmartApp"
|
||||
]
|
||||
])
|
||||
log.debug "Returning ${jsonOut}"
|
||||
openhabDevice.deviceNotification(jsonOut)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the attribute is on this capability
|
||||
if (! capability.attributes.contains(json.capabilityAttribute) ) {
|
||||
log.error "Capability \"${json.capabilityKey}\" does NOT contain the expected attribute: \"${json.capabilityAttribute}\", make sure the a CAPABILITY_MAP for this capability contains the missing attribte."
|
||||
def jsonOut = new JsonOutput().toJson([
|
||||
path: "/smartthings/error",
|
||||
body: [
|
||||
message: "Requested current state information for CAPABILITY: \"${json.capabilityKey}\" with attribute: \"${json.capabilityAttribute}\" but this is attribute not defined for this capability in the SmartApp"
|
||||
]
|
||||
])
|
||||
log.debug "Returning ${jsonOut}"
|
||||
openhabDevice.deviceNotification(jsonOut)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Look for the device associated with this capability and return the value of the specified attribute
|
||||
settings[json.capabilityKey].each {device ->
|
||||
if (device.displayName == json.deviceDisplayName) {
|
||||
// Have the device, get the value and return the correct message
|
||||
def currentState = device.currentValue(json.capabilityAttribute)
|
||||
def jsonOut = new JsonOutput().toJson([
|
||||
path: "/smartthings/state",
|
||||
body: [
|
||||
deviceDisplayName: device.displayName,
|
||||
capabilityAttribute: json.capabilityAttribute,
|
||||
value: currentState
|
||||
]
|
||||
])
|
||||
|
||||
log.debug "Returning ${jsonOut}"
|
||||
openhabDevice.deviceNotification(jsonOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update a device when requested from OpenHAB
|
||||
def openhabUpdateHandler(evt) {
|
||||
def json = new JsonSlurper().parseText(evt.value)
|
||||
log.debug "Received update event from openhabDevice: ${json}"
|
||||
|
||||
if (json.type == "notify") {
|
||||
if (json.name == "Contacts") {
|
||||
sendNotificationToContacts("${json.value}", recipients)
|
||||
} else {
|
||||
sendNotificationEvent("${json.value}")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the CAPABILITY_MAP entry for this device type
|
||||
def capability = CAPABILITY_MAP[json.capabilityKey]
|
||||
if (capability == null) {
|
||||
log.error "No capability: \"${json.capabilityKey}\" exists, make sure there is a CAPABILITY_MAP entry for this capability."
|
||||
def jsonOut = new JsonOutput().toJson([
|
||||
path: "/smartthings/error",
|
||||
body: [
|
||||
message: "Update failed device displayName of: \"${json.deviceDisplayName}\" with CAPABILITY: \"${json.capabilityKey}\" because that CAPABILTY does not exist in the SmartApp"
|
||||
]
|
||||
])
|
||||
log.debug "Returning ${jsonOut}"
|
||||
openhabDevice.deviceNotification(jsonOut)
|
||||
return
|
||||
}
|
||||
|
||||
// Look for the device associated with this capability and perform the requested action
|
||||
settings[json.capabilityKey].each {device ->
|
||||
if (device.displayName == json.deviceDisplayName) {
|
||||
// log.debug "openhabUpdateHandler - found device for ${json.deviceDisplayName}"
|
||||
if (capability.containsKey("action")) {
|
||||
log.debug "openhabUpdateHandler - Capability ${capability.name} with device name ${device.displayName} changed to ${json.value} using action ${capability.action}"
|
||||
def action = capability["action"]
|
||||
// Yes, this is calling the method dynamically
|
||||
"$action"(device, json.capabilityAttribute, json.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send a list of all devices to OpenHAB - used during OpenHAB's discovery process
|
||||
def openhabDiscoveryHandler(evt) {
|
||||
log.debug "Entered discovery handler"
|
||||
def results = []
|
||||
|
||||
CAPABILITY_MAP.each { key, capability ->
|
||||
capability["attributes"].each { attribute ->
|
||||
settings[key].each {device ->
|
||||
// The device info has to be returned as a string. It will be parsed into device data on the OpenHAB side
|
||||
def deviceInfo = "{\"capability\": \"${key}\", \"attribute\": \"${attribute}\", \"name\": \"${device.displayName}\", \"id\": \"${device.id}\" }"
|
||||
results.push(deviceInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def json = new groovy.json.JsonOutput().toJson([
|
||||
path: "/smartthings/discovery",
|
||||
body: results
|
||||
])
|
||||
|
||||
log.debug "Discovery is returning JSON: ${json}"
|
||||
|
||||
openhabDevice.deviceNotification(json)
|
||||
}
|
||||
|
||||
// Receive an event from a device and send it onto OpenHAB
|
||||
def inputHandler(evt) {
|
||||
def device = evt.device
|
||||
def capabilities = device.capabilities
|
||||
|
||||
def json = new JsonOutput().toJson([
|
||||
path: "/smartthings/state",
|
||||
body: [
|
||||
deviceDisplayName: evt.displayName,
|
||||
value: evt.value,
|
||||
capabilityAttribute: evt.name
|
||||
]
|
||||
])
|
||||
|
||||
log.debug "Forwarding device event to openhabDevice: ${json}"
|
||||
openhabDevice.deviceNotification(json)
|
||||
}
|
||||
|
||||
|
||||
// +---------------------------------+
|
||||
// | WARNING, BEYOND HERE BE DRAGONS |
|
||||
// +---------------------------------+
|
||||
// These are the functions that handle incoming messages from MQTT.
|
||||
// I tried to put them in closures but apparently SmartThings Groovy sandbox
|
||||
// restricts you from running clsures from an object (it's not safe).
|
||||
|
||||
def actionAlarm(device, attribute, value) {
|
||||
switch (value) {
|
||||
case "strobe":
|
||||
device.strobe()
|
||||
break
|
||||
case "siren":
|
||||
device.siren()
|
||||
break
|
||||
case "off":
|
||||
device.off()
|
||||
break
|
||||
case "both":
|
||||
device.both()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def actionColor(device, attribute, value) {
|
||||
switch (attribute) {
|
||||
case "hue":
|
||||
device.setHue(value as float)
|
||||
break
|
||||
case "saturation":
|
||||
device.setSaturation(value as float)
|
||||
break
|
||||
case "color":
|
||||
def values = value.split(',')
|
||||
def colormap = ["hue": values[0] as float, "saturation": values[1] as float]
|
||||
device.setColor(colormap)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def actionOpenClosed(device, attribute, value) {
|
||||
if (value == "open") {
|
||||
device.open()
|
||||
} else if (value == "close") {
|
||||
device.close()
|
||||
}
|
||||
}
|
||||
|
||||
def actionOnOff(device, attribute, value) {
|
||||
if (value == "off") {
|
||||
device.off()
|
||||
} else if (value == "on") {
|
||||
device.on()
|
||||
}
|
||||
}
|
||||
|
||||
def actionActiveInactive(device, attribute, value) {
|
||||
if (value == "active") {
|
||||
device.active()
|
||||
} else if (value == "inactive") {
|
||||
device.inactive()
|
||||
}
|
||||
}
|
||||
|
||||
def actionThermostat(device, attribute, value) {
|
||||
switch(attribute) {
|
||||
case "heatingSetpoint":
|
||||
device.setHeatingSetpoint(value)
|
||||
break
|
||||
case "coolingSetpoint":
|
||||
device.setCoolingSetpoint(value)
|
||||
break
|
||||
case "thermostatMode":
|
||||
device.setThermostatMode(value)
|
||||
break
|
||||
case "thermostatFanMode":
|
||||
device.setThermostatFanMode(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def actionMusicPlayer(device, attribute, value) {
|
||||
switch(attribute) {
|
||||
case "level":
|
||||
device.setLevel(value)
|
||||
break
|
||||
case "mute":
|
||||
if (value == "muted") {
|
||||
device.mute()
|
||||
} else if (value == "unmuted") {
|
||||
device.unmute()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def actionColorTemperature(device, attribute, value) {
|
||||
device.setColorTemperature(value as int)
|
||||
}
|
||||
|
||||
def actionLevel(device, attribute, value) {
|
||||
log.debug "Setting device ${device} with attribute ${attribute} to value ${value}"
|
||||
device.setLevel(value as int)
|
||||
}
|
||||
|
||||
def actionConsumable(device, attribute, value) {
|
||||
device.setConsumableStatus(value)
|
||||
}
|
||||
|
||||
def actionLock(device, attribute, value) {
|
||||
if (value == "locked") {
|
||||
device.lock()
|
||||
} else if (value == "unlocked") {
|
||||
device.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
def actionCoolingThermostat(device, attribute, value) {
|
||||
device.setCoolingSetpoint(value)
|
||||
}
|
||||
|
||||
def actionThermostatFan(device, attribute, value) {
|
||||
device.setThermostatFanMode(value)
|
||||
}
|
||||
|
||||
def actionHeatingThermostat(device, attribute, value) {
|
||||
device.setHeatingSetpoint(value)
|
||||
}
|
||||
|
||||
def actionThermostatMode(device, attribute, value) {
|
||||
device.setThermostatMode(value)
|
||||
}
|
||||
|
||||
def actionTimedSession(device, attribute, value) {
|
||||
if (attribute == "timeRemaining") {
|
||||
device.setTimeRemaining(value)
|
||||
}
|
||||
}
|
||||
|
||||
// The following functions return the current state of a device
|
||||
def switchState(device, attribute) {
|
||||
device.currentValue(attribute);
|
||||
}
|
||||
704
smartapps/stj/mqtt-bridge.src/mqtt-bridge.groovy
Normal file
704
smartapps/stj/mqtt-bridge.src/mqtt-bridge.groovy
Normal file
@@ -0,0 +1,704 @@
|
||||
/**
|
||||
* MQTT Bridge
|
||||
*
|
||||
* Authors
|
||||
* - st.john.johnson@gmail.com
|
||||
* - jeremiah.wuenschel@gmail.com
|
||||
*
|
||||
* Copyright 2016
|
||||
*
|
||||
* 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
|
||||
import groovy.transform.Field
|
||||
|
||||
// Massive lookup tree
|
||||
@Field CAPABILITY_MAP = [
|
||||
"accelerationSensors": [
|
||||
name: "Acceleration Sensor",
|
||||
capability: "capability.accelerationSensor",
|
||||
attributes: [
|
||||
"acceleration"
|
||||
]
|
||||
],
|
||||
"alarm": [
|
||||
name: "Alarm",
|
||||
capability: "capability.alarm",
|
||||
attributes: [
|
||||
"alarm"
|
||||
],
|
||||
action: "actionAlarm"
|
||||
],
|
||||
"battery": [
|
||||
name: "Battery",
|
||||
capability: "capability.battery",
|
||||
attributes: [
|
||||
"battery"
|
||||
]
|
||||
],
|
||||
"beacon": [
|
||||
name: "Beacon",
|
||||
capability: "capability.beacon",
|
||||
attributes: [
|
||||
"presence"
|
||||
]
|
||||
],
|
||||
"button": [
|
||||
name: "Button",
|
||||
capability: "capability.button",
|
||||
attributes: [
|
||||
"button"
|
||||
]
|
||||
],
|
||||
"carbonDioxideMeasurement": [
|
||||
name: "Carbon Dioxide Measurement",
|
||||
capability: "capability.carbonDioxideMeasurement",
|
||||
attributes: [
|
||||
"carbonDioxide"
|
||||
]
|
||||
],
|
||||
"carbonMonoxideDetector": [
|
||||
name: "Carbon Monoxide Detector",
|
||||
capability: "capability.carbonMonoxideDetector",
|
||||
attributes: [
|
||||
"carbonMonoxide"
|
||||
]
|
||||
],
|
||||
"colorControl": [
|
||||
name: "Color Control",
|
||||
capability: "capability.colorControl",
|
||||
attributes: [
|
||||
"hue",
|
||||
"saturation",
|
||||
"color"
|
||||
],
|
||||
action: "actionColor"
|
||||
],
|
||||
"colorTemperature": [
|
||||
name: "Color Temperature",
|
||||
capability: "capability.colorTemperature",
|
||||
attributes: [
|
||||
"colorTemperature"
|
||||
],
|
||||
action: "actionColorTemperature"
|
||||
],
|
||||
"consumable": [
|
||||
name: "Consumable",
|
||||
capability: "capability.consumable",
|
||||
attributes: [
|
||||
"consumable"
|
||||
],
|
||||
action: "actionConsumable"
|
||||
],
|
||||
"contactSensors": [
|
||||
name: "Contact Sensor",
|
||||
capability: "capability.contactSensor",
|
||||
attributes: [
|
||||
"contact"
|
||||
]
|
||||
],
|
||||
"doorControl": [
|
||||
name: "Door Control",
|
||||
capability: "capability.doorControl",
|
||||
attributes: [
|
||||
"door"
|
||||
],
|
||||
action: "actionOpenClosed"
|
||||
],
|
||||
"energyMeter": [
|
||||
name: "Energy Meter",
|
||||
capability: "capability.energyMeter",
|
||||
attributes: [
|
||||
"energy"
|
||||
]
|
||||
],
|
||||
"garageDoors": [
|
||||
name: "Garage Door Control",
|
||||
capability: "capability.garageDoorControl",
|
||||
attributes: [
|
||||
"door"
|
||||
],
|
||||
action: "actionOpenClosed"
|
||||
],
|
||||
"illuminanceMeasurement": [
|
||||
name: "Illuminance Measurement",
|
||||
capability: "capability.illuminanceMeasurement",
|
||||
attributes: [
|
||||
"illuminance"
|
||||
]
|
||||
],
|
||||
"imageCapture": [
|
||||
name: "Image Capture",
|
||||
capability: "capability.imageCapture",
|
||||
attributes: [
|
||||
"image"
|
||||
]
|
||||
],
|
||||
"levels": [
|
||||
name: "Switch Level",
|
||||
capability: "capability.switchLevel",
|
||||
attributes: [
|
||||
"level"
|
||||
],
|
||||
action: "actionLevel"
|
||||
],
|
||||
"lock": [
|
||||
name: "Lock",
|
||||
capability: "capability.lock",
|
||||
attributes: [
|
||||
"lock"
|
||||
],
|
||||
action: "actionLock"
|
||||
],
|
||||
"mediaController": [
|
||||
name: "Media Controller",
|
||||
capability: "capability.mediaController",
|
||||
attributes: [
|
||||
"activities",
|
||||
"currentActivity"
|
||||
]
|
||||
],
|
||||
"motionSensors": [
|
||||
name: "Motion Sensor",
|
||||
capability: "capability.motionSensor",
|
||||
attributes: [
|
||||
"motion"
|
||||
],
|
||||
action: "actionActiveInactive"
|
||||
],
|
||||
"musicPlayer": [
|
||||
name: "Music Player",
|
||||
capability: "capability.musicPlayer",
|
||||
attributes: [
|
||||
"status",
|
||||
"level",
|
||||
"trackDescription",
|
||||
"trackData",
|
||||
"mute"
|
||||
],
|
||||
action: "actionMusicPlayer"
|
||||
],
|
||||
"pHMeasurement": [
|
||||
name: "pH Measurement",
|
||||
capability: "capability.pHMeasurement",
|
||||
attributes: [
|
||||
"pH"
|
||||
]
|
||||
],
|
||||
"powerMeters": [
|
||||
name: "Power Meter",
|
||||
capability: "capability.powerMeter",
|
||||
attributes: [
|
||||
"power"
|
||||
]
|
||||
],
|
||||
"presenceSensors": [
|
||||
name: "Presence Sensor",
|
||||
capability: "capability.presenceSensor",
|
||||
attributes: [
|
||||
"presence"
|
||||
],
|
||||
action: "actionPresence"
|
||||
],
|
||||
"humiditySensors": [
|
||||
name: "Relative Humidity Measurement",
|
||||
capability: "capability.relativeHumidityMeasurement",
|
||||
attributes: [
|
||||
"humidity"
|
||||
]
|
||||
],
|
||||
"relaySwitch": [
|
||||
name: "Relay Switch",
|
||||
capability: "capability.relaySwitch",
|
||||
attributes: [
|
||||
"switch"
|
||||
],
|
||||
action: "actionOnOff"
|
||||
],
|
||||
"shockSensor": [
|
||||
name: "Shock Sensor",
|
||||
capability: "capability.shockSensor",
|
||||
attributes: [
|
||||
"shock"
|
||||
]
|
||||
],
|
||||
"signalStrength": [
|
||||
name: "Signal Strength",
|
||||
capability: "capability.signalStrength",
|
||||
attributes: [
|
||||
"lqi",
|
||||
"rssi"
|
||||
]
|
||||
],
|
||||
"sleepSensor": [
|
||||
name: "Sleep Sensor",
|
||||
capability: "capability.sleepSensor",
|
||||
attributes: [
|
||||
"sleeping"
|
||||
]
|
||||
],
|
||||
"smokeDetector": [
|
||||
name: "Smoke Detector",
|
||||
capability: "capability.smokeDetector",
|
||||
attributes: [
|
||||
"smoke"
|
||||
]
|
||||
],
|
||||
"soundSensor": [
|
||||
name: "Sound Sensor",
|
||||
capability: "capability.soundSensor",
|
||||
attributes: [
|
||||
"sound"
|
||||
]
|
||||
],
|
||||
"stepSensor": [
|
||||
name: "Step Sensor",
|
||||
capability: "capability.stepSensor",
|
||||
attributes: [
|
||||
"steps",
|
||||
"goal"
|
||||
]
|
||||
],
|
||||
"switches": [
|
||||
name: "Switch",
|
||||
capability: "capability.switch",
|
||||
attributes: [
|
||||
"switch"
|
||||
],
|
||||
action: "actionOnOff"
|
||||
],
|
||||
"soundPressureLevel": [
|
||||
name: "Sound Pressure Level",
|
||||
capability: "capability.soundPressureLevel",
|
||||
attributes: [
|
||||
"soundPressureLevel"
|
||||
]
|
||||
],
|
||||
"tamperAlert": [
|
||||
name: "Tamper Alert",
|
||||
capability: "capability.tamperAlert",
|
||||
attributes: [
|
||||
"tamper"
|
||||
]
|
||||
],
|
||||
"temperatureSensors": [
|
||||
name: "Temperature Measurement",
|
||||
capability: "capability.temperatureMeasurement",
|
||||
attributes: [
|
||||
"temperature"
|
||||
]
|
||||
],
|
||||
"thermostat": [
|
||||
name: "Thermostat",
|
||||
capability: "capability.thermostat",
|
||||
attributes: [
|
||||
"temperature",
|
||||
"heatingSetpoint",
|
||||
"coolingSetpoint",
|
||||
"thermostatSetpoint",
|
||||
"thermostatMode",
|
||||
"thermostatFanMode",
|
||||
"thermostatOperatingState"
|
||||
],
|
||||
action: "actionThermostat"
|
||||
],
|
||||
"thermostatCoolingSetpoint": [
|
||||
name: "Thermostat Cooling Setpoint",
|
||||
capability: "capability.thermostatCoolingSetpoint",
|
||||
attributes: [
|
||||
"coolingSetpoint"
|
||||
],
|
||||
action: "actionCoolingThermostat"
|
||||
],
|
||||
"thermostatFanMode": [
|
||||
name: "Thermostat Fan Mode",
|
||||
capability: "capability.thermostatFanMode",
|
||||
attributes: [
|
||||
"thermostatFanMode"
|
||||
],
|
||||
action: "actionThermostatFan"
|
||||
],
|
||||
"thermostatHeatingSetpoint": [
|
||||
name: "Thermostat Heating Setpoint",
|
||||
capability: "capability.thermostatHeatingSetpoint",
|
||||
attributes: [
|
||||
"heatingSetpoint"
|
||||
],
|
||||
action: "actionHeatingThermostat"
|
||||
],
|
||||
"thermostatMode": [
|
||||
name: "Thermostat Mode",
|
||||
capability: "capability.thermostatMode",
|
||||
attributes: [
|
||||
"thermostatMode"
|
||||
],
|
||||
action: "actionThermostatMode"
|
||||
],
|
||||
"thermostatOperatingState": [
|
||||
name: "Thermostat Operating State",
|
||||
capability: "capability.thermostatOperatingState",
|
||||
attributes: [
|
||||
"thermostatOperatingState"
|
||||
]
|
||||
],
|
||||
"thermostatSetpoint": [
|
||||
name: "Thermostat Setpoint",
|
||||
capability: "capability.thermostatSetpoint",
|
||||
attributes: [
|
||||
"thermostatSetpoint"
|
||||
]
|
||||
],
|
||||
"threeAxis": [
|
||||
name: "Three Axis",
|
||||
capability: "capability.threeAxis",
|
||||
attributes: [
|
||||
"threeAxis"
|
||||
]
|
||||
],
|
||||
"timedSession": [
|
||||
name: "Timed Session",
|
||||
capability: "capability.timedSession",
|
||||
attributes: [
|
||||
"timeRemaining",
|
||||
"sessionStatus"
|
||||
],
|
||||
action: "actionTimedSession"
|
||||
],
|
||||
"touchSensor": [
|
||||
name: "Touch Sensor",
|
||||
capability: "capability.touchSensor",
|
||||
attributes: [
|
||||
"touch"
|
||||
]
|
||||
],
|
||||
"valve": [
|
||||
name: "Valve",
|
||||
capability: "capability.valve",
|
||||
attributes: [
|
||||
"contact"
|
||||
],
|
||||
action: "actionOpenClosed"
|
||||
],
|
||||
"voltageMeasurement": [
|
||||
name: "Voltage Measurement",
|
||||
capability: "capability.voltageMeasurement",
|
||||
attributes: [
|
||||
"voltage"
|
||||
]
|
||||
],
|
||||
"waterSensors": [
|
||||
name: "Water Sensor",
|
||||
capability: "capability.waterSensor",
|
||||
attributes: [
|
||||
"water"
|
||||
]
|
||||
],
|
||||
"windowShades": [
|
||||
name: "Window Shade",
|
||||
capability: "capability.windowShade",
|
||||
attributes: [
|
||||
"windowShade"
|
||||
],
|
||||
action: "actionOpenClosed"
|
||||
]
|
||||
]
|
||||
|
||||
definition(
|
||||
name: "MQTT Bridge",
|
||||
namespace: "stj",
|
||||
author: "St. John Johnson and Jeremiah Wuenschel",
|
||||
description: "A bridge between SmartThings and MQTT",
|
||||
category: "My Apps",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Connections/Cat-Connections.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Connections/Cat-Connections@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Connections/Cat-Connections@3x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
section("Send Notifications?") {
|
||||
input("recipients", "contact", title: "Send notifications to", multiple: true, required: false)
|
||||
}
|
||||
|
||||
section ("Input") {
|
||||
CAPABILITY_MAP.each { key, capability ->
|
||||
input key, capability["capability"], title: capability["name"], multiple: true, required: false
|
||||
}
|
||||
}
|
||||
|
||||
section ("Bridge") {
|
||||
input "bridge", "capability.notification", title: "Notify this Bridge", required: true, multiple: false
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
|
||||
runEvery15Minutes(initialize)
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
// Unsubscribe from all events
|
||||
unsubscribe()
|
||||
// Subscribe to stuff
|
||||
initialize()
|
||||
}
|
||||
|
||||
// Return list of displayNames
|
||||
def getDeviceNames(devices) {
|
||||
def list = []
|
||||
devices.each{device->
|
||||
list.push(device.displayName)
|
||||
}
|
||||
list
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
// Subscribe to new events from devices
|
||||
CAPABILITY_MAP.each { key, capability ->
|
||||
capability["attributes"].each { attribute ->
|
||||
subscribe(settings[key], attribute, inputHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to events from the bridge
|
||||
subscribe(bridge, "message", bridgeHandler)
|
||||
|
||||
// Update the bridge
|
||||
updateSubscription()
|
||||
}
|
||||
|
||||
// Update the bridge"s subscription
|
||||
def updateSubscription() {
|
||||
def attributes = [
|
||||
notify: ["Contacts", "System"]
|
||||
]
|
||||
CAPABILITY_MAP.each { key, capability ->
|
||||
capability["attributes"].each { attribute ->
|
||||
if (!attributes.containsKey(attribute)) {
|
||||
attributes[attribute] = []
|
||||
}
|
||||
settings[key].each {device ->
|
||||
attributes[attribute].push(device.displayName)
|
||||
}
|
||||
}
|
||||
}
|
||||
def json = new groovy.json.JsonOutput().toJson([
|
||||
path: "/subscribe",
|
||||
body: [
|
||||
devices: attributes
|
||||
]
|
||||
])
|
||||
|
||||
log.debug "Updating subscription: ${json}"
|
||||
|
||||
bridge.deviceNotification(json)
|
||||
}
|
||||
|
||||
// Receive an event from the bridge
|
||||
def bridgeHandler(evt) {
|
||||
def json = new JsonSlurper().parseText(evt.value)
|
||||
log.debug "Received device event from bridge: ${json}"
|
||||
|
||||
if (json.type == "notify") {
|
||||
if (json.name == "Contacts") {
|
||||
sendNotificationToContacts("${json.value}", recipients)
|
||||
} else {
|
||||
sendNotificationEvent("${json.value}")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// @NOTE this is stored AWFUL, we need a faster lookup table
|
||||
// @NOTE this also has no fast fail, I need to look into how to do that
|
||||
CAPABILITY_MAP.each { key, capability ->
|
||||
if (capability["attributes"].contains(json.type)) {
|
||||
settings[key].each {device ->
|
||||
if (device.displayName == json.name) {
|
||||
if (capability.containsKey("action")) {
|
||||
def action = capability["action"]
|
||||
// Yes, this is calling the method dynamically
|
||||
"$action"(device, json.type, json.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Receive an event from a device
|
||||
def inputHandler(evt) {
|
||||
def json = new JsonOutput().toJson([
|
||||
path: "/push",
|
||||
body: [
|
||||
name: evt.displayName,
|
||||
value: evt.value,
|
||||
type: evt.name
|
||||
]
|
||||
])
|
||||
|
||||
log.debug "Forwarding device event to bridge: ${json}"
|
||||
bridge.deviceNotification(json)
|
||||
}
|
||||
|
||||
// +---------------------------------+
|
||||
// | WARNING, BEYOND HERE BE DRAGONS |
|
||||
// +---------------------------------+
|
||||
// These are the functions that handle incoming messages from MQTT.
|
||||
// I tried to put them in closures but apparently SmartThings Groovy sandbox
|
||||
// restricts you from running clsures from an object (it's not safe).
|
||||
|
||||
def actionAlarm(device, attribute, value) {
|
||||
switch (value) {
|
||||
case "strobe":
|
||||
device.strobe()
|
||||
break
|
||||
case "siren":
|
||||
device.siren()
|
||||
break
|
||||
case "off":
|
||||
device.off()
|
||||
break
|
||||
case "both":
|
||||
device.both()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def actionColor(device, attribute, value) {
|
||||
switch (attribute) {
|
||||
case "hue":
|
||||
device.setHue(value as float)
|
||||
break
|
||||
case "saturation":
|
||||
device.setSaturation(value as float)
|
||||
break
|
||||
case "color":
|
||||
def values = value.split(',')
|
||||
def colormap = ["hue": values[0] as float, "saturation": values[1] as float]
|
||||
device.setColor(colormap)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def actionOpenClosed(device, attribute, value) {
|
||||
if (value == "open") {
|
||||
device.open()
|
||||
} else if (value == "closed") {
|
||||
device.close()
|
||||
}
|
||||
}
|
||||
|
||||
def actionOnOff(device, attribute, value) {
|
||||
if (value == "off") {
|
||||
device.off()
|
||||
} else if (value == "on") {
|
||||
device.on()
|
||||
}
|
||||
}
|
||||
|
||||
def actionActiveInactive(device, attribute, value) {
|
||||
if (value == "active") {
|
||||
device.active()
|
||||
} else if (value == "inactive") {
|
||||
device.inactive()
|
||||
}
|
||||
}
|
||||
|
||||
def actionThermostat(device, attribute, value) {
|
||||
switch(attribute) {
|
||||
case "heatingSetpoint":
|
||||
device.setHeatingSetpoint(value)
|
||||
break
|
||||
case "coolingSetpoint":
|
||||
device.setCoolingSetpoint(value)
|
||||
break
|
||||
case "thermostatMode":
|
||||
device.setThermostatMode(value)
|
||||
break
|
||||
case "thermostatFanMode":
|
||||
device.setThermostatFanMode(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def actionMusicPlayer(device, attribute, value) {
|
||||
switch(attribute) {
|
||||
case "level":
|
||||
device.setLevel(value)
|
||||
break
|
||||
case "mute":
|
||||
if (value == "muted") {
|
||||
device.mute()
|
||||
} else if (value == "unmuted") {
|
||||
device.unmute()
|
||||
}
|
||||
break
|
||||
case "status":
|
||||
if (device.getSupportedCommands().any {it.name == "setStatus"}) {
|
||||
device.setStatus(value)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def actionColorTemperature(device, attribute, value) {
|
||||
device.setColorTemperature(value as int)
|
||||
}
|
||||
|
||||
def actionLevel(device, attribute, value) {
|
||||
device.setLevel(value as int)
|
||||
}
|
||||
|
||||
def actionPresence(device, attribute, value) {
|
||||
if (value == "present") {
|
||||
device.arrived();
|
||||
}
|
||||
else if (value == "not present") {
|
||||
device.departed();
|
||||
}
|
||||
}
|
||||
|
||||
def actionConsumable(device, attribute, value) {
|
||||
device.setConsumableStatus(value)
|
||||
}
|
||||
|
||||
def actionLock(device, attribute, value) {
|
||||
if (value == "locked") {
|
||||
device.lock()
|
||||
} else if (value == "unlocked") {
|
||||
device.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
def actionCoolingThermostat(device, attribute, value) {
|
||||
device.setCoolingSetpoint(value)
|
||||
}
|
||||
|
||||
def actionThermostatFan(device, attribute, value) {
|
||||
device.setThermostatFanMode(value)
|
||||
}
|
||||
|
||||
def actionHeatingThermostat(device, attribute, value) {
|
||||
device.setHeatingSetpoint(value)
|
||||
}
|
||||
|
||||
def actionThermostatMode(device, attribute, value) {
|
||||
device.setThermostatMode(value)
|
||||
}
|
||||
|
||||
def actionTimedSession(device, attribute, value) {
|
||||
if (attribute == "timeRemaining") {
|
||||
device.setTimeRemaining(value)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user