Thanks. I just added the debug line today as I was finally wanting to get things working again.
The code was borrowed from a MQTT bridge. There is a custom device handler code (below), virtual devices (like switches), and an custom smartapp that acts as a bridge. In this manner, I was using SmartThings as a GUI for my custom home automation system.
I can still send updates and see the virtual devices change state, so commands from my home automation system are getting sent to the bridge app and processed.
The part that isn’t working is when I click on a virtual switch in the SmartThings app, I see the information in the debug log on the groovy portal, but it is not being sent over the local network like it used to. The code below had been working for years.
/**
// 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”
],
// ETC CHANGE ADDED ABILITY TO UPDATE BATTERY
action: “actionBattery”
],
“beacon”: [
name: “Beacon”,
capability: “capability.beacon”,
attributes: [
“presence”
]
],
“button”: [
name: “Button”,
capability: “capability.button”,
attributes: [
“button”
],
//ETC CHANGE ADDED BUTTON UPDATE CAPABILITY
action: “actionButton”
],
// ETC ADDED
“execute”: [
name: “Execute”,
capability: “capability.execute”,
attributes: [
“armMode”,
“data”
],
action: “actionExecute”
],
“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”
],
// ETC CHANGE ADDED ABILITY TO UPDATE CONTACT SENSORS
action: “actionOpenClosed”
],
“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”
],
// ETC CHANGE: ADD GEOLOCATION
“geolocations”: [
name: “Geolocation”,
capability: “capability.geolocation”,
attributes: [
“longitude”,
“latitude”,
“accuracy”,
“altitudeAccuracy”,
“speed”,
“lastUpdateTime”
],
],
“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”
],
// ETC CHANGE ADD AUDIO VOLUME
“volumes”: [
name: “Audio Volume”,
capability: “capability.audioVolume”,
attributes: [
“volume”
],
action: “actionVolume”
],
“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”
],
// ETC CHANGE ADDED ABILITY TO UPDATE TEMPERATURE
action: “actionTemperature”
],
“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: “PremiseBridgeApp”,
namespace: “smartthings”,
author: “ETC”,
description: “A bridge between SmartThings and Premise”,
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: false, 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)
}
}
}
// ETC CHANGE: no need to do this for HTTP connection, this was for server to update MQTT
// def json = new groovy.json.JsonOutput().toJson([
// path: “/subscribe”,
// body: [
// devices: attributes
// ]
// ])
log.debug "PREMISE APP: Updating subscription: ${json}"
// bridge.deviceNotification(json)
}
// Receive an event from the bridge
// AKA Incoming command from Premise
def bridgeHandler(evt) {
def json = new JsonSlurper().parseText(evt.value)
log.debug “PREMISE APP: Received device event from bridge: ${json}”
if (json == null) {
} else {
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 (json.command == false) {
if (device.getSupportedCommands().any {it.name == "setStatus"}) {
log.debug "PREMISE APP: Setting state ${json.type} = ${json.value}"
device.setStatus(json.type, json.value)
state.ignoreEvent = json;
}
}
else {
if (capability.containsKey("action")) {
def action = capability["action"]
log.debug "PREMISE APP: Setting action ${device} ${json.type} = ${json.value} method:${action}"
// ETC ADDED THE LINE BELOW TO HELP PREVENT CONTROL LOOP
state.ignoreEvent = json;
state.lastSent = json;
// Yes, this is calling the method dynamically
"$action"(device, json.type, json.value)
}
}
}
}
}
}
}
}
// Receive an event from a device
def inputHandler(evt) {
if (
state.ignoreEvent
&& state.ignoreEvent.name == evt.displayName
&& state.ignoreEvent.type == evt.name
&& state.ignoreEvent.value == evt.value
) {
log.debug “PREMISE APP: Ignoring event ${state.ignoreEvent}”
// ETC NOTES: reset ignore flag object
state.ignoreEvent = false;
} else if(
// ETC CHANGE: this was added for odd case where I arm alarm from Premise, but 3 events were still sent following ignore flag being cleared
// I think this is due to SmartHome Monitor setting these events again, and then being picked up.
state.lastSent
&& state.lastSent.name == evt.displayName
&& state.lastSent.type == evt.name
&& state.lastSent.value == evt.value
&& evt.name == “armMode”){
log.debug "PREMISE APP: duplicate arm mode command ${state.lastSent}"
}
else {
// ETC CHANGE attempt to send brightness instead of power state to avoid possible control loop
def sDisplayName = evt.displayName
def sValue = evt.value
def sName = evt.name
String sData = evt.data
// ETC CHANGE: trick SmartThings Media Player into allowing undefined attributes (tried Execute capability like with alarm keypad, but no luck)
if ((sValue == "playing") && (sName == "status")) {
try{
sValue = sData.substring(sData.indexOf("{((")+3,sData.indexOf("))}"))
log.debug "PREMISE APP: processing media zone undefined attribute: ${sValue}"
} catch (StringIndexOutOfBoundsException e) {
log.debug "PREMISE APP: processing media zone attribute: ${sValue}"
}
}
if ((sName == "latitude") || (sName == "longitude")) {
//def attrs = evt.device.supportedAttributes
def attrs = evt.device.capabilities
attrs.each {
//log.debug "TRACKER: attribute ${it.name}, values: ${it.values}"
log.debug "TRACKER: capability ${it.name}"
//log.debug "TRACKER: attribute ${it.name}, dataType: ${it.dataType}"
}
//sLong = device.longitude
//sLat = device.latitude
//sAccuracy = device.accuracy
//log.debug "GEOLOCATION IS: Long:${sLong} LAT:${sLat} ACCURACY:${sAccuracy}"
}
// ETC NOTES: we finally send the json to Premise via the Premise Bridge device handler
def json = new JsonOutput().toJson([
path: "/push",
body: [
name: sDisplayName,
value: sValue,
type: sName
]
])
log.debug "PREMISE APP: Forwarding device event to Premise: ${json}"
bridge.deviceNotification(json)
}
}
// ±--------------------------------+
// | WARNING, BEYOND HERE BE DRAGONS |
// ±--------------------------------+
// These are the functions that handle incoming messages from Premise.
// 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()
}
}
//ETC CHANGE ADDED BUTTON UPDATES FROM PREMISE
def actionButton(device, attribute, value) {
if (value == “pushed”) {
device.pushed()
} else if (value == “held”) {
device.held()
}
}
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()
}
}
// ETC CHANGE ADD AUDIO VOLUME
def actionVolume(device, attribute, value) {
device.setVolume(value as int)
}
def actionThermostat(device, attribute, value) {
switch(attribute) {
case “heatingSetpoint”:
device.setHeatingSetpoint(value as int)
break
case “coolingSetpoint”:
device.setCoolingSetpoint(value as int)
break
case “thermostatMode”:
device.setThermostatMode(value)
break
case “thermostatFanMode”:
device.setThermostatFanMode(value)
break
// ETC CHANGE ADDED THESE SO PREMISE CAN UPDATE THEM
case “temperature”:
device.setTemperature(value as int)
break
case “thermostatSetpoint”:
device.setThermostatSetpoint(value as int)
break
case “thermostatOperatingState”:
device.setThermostatOperatingState(value)
}
}
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
case “trackDescription”:
device.setTrackDescription(value)
}
}
def actionColorTemperature(device, attribute, value) {
device.setColorTemperature(value as int)
}
// ETC CHANGE: Added a call to setBatteryLevel as the SmartThings lock simulator supported this method.
def actionBattery(device, attribute, value) {
device.setBatteryLevel(value as int)
}
// ETC CHANGE: Added alarm keypad actions to execute
def actionExecute(device, attribute, value) {
switch(attribute) {
case “armMode”:
switch(value) {
case “disarmed”:
device.setDisarmed()
break
case “armedStay”:
device.setArmedStay()
break
case “armedAway”:
device.setArmedAway()
break
}
break
}
}
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 as int)
}
// ETC CHANGE: add setTemperature (supported by thermostat custom device handler)
def actionTemperature(device, attribute, value) {
device.setTemperature(value as int)
}
def actionThermostatFan(device, attribute, value) {
device.setThermostatFanMode(value)
}
def actionHeatingThermostat(device, attribute, value) {
device.setHeatingSetpoint(value as int)
}
def actionThermostatMode(device, attribute, value) {
device.setThermostatMode(value)
}
def actionTimedSession(device, attribute, value) {
if (attribute == “timeRemaining”) {
device.setTimeRemaining(value)
}
}