diff --git a/server.js b/server.js index ca8d43b..40ad398 100644 --- a/server.js +++ b/server.js @@ -1,3 +1,4 @@ + /*jslint node: true */ 'use strict'; @@ -195,8 +196,12 @@ function handleSubscribeEvent (req, res) { subscriptions = []; Object.keys(req.body.devices).forEach(function (property) { req.body.devices[property].forEach(function (device) { - subscriptions.push(getTopicFor(device, property, TOPIC_COMMAND)); - subscriptions.push(getTopicFor(device, property, TOPIC_WRITE_STATE)); + var cmdTopic = getTopicFor(device, property, TOPIC_COMMAND); + var writeTopic = getTopicFor(device, property, TOPIC_WRITE_STATE); + subscriptions.push(cmdTopic); + if (writeTopic !== cmdTopic) { + subscriptions.push(writeTopic); + } }); }); @@ -251,14 +256,13 @@ function getTopicFor (device, property, type) { */ function parseMQTTMessage (topic, message) { var contents = message.toString(); - winston.info('Incoming message from MQTT: %s = %s', topic, contents); + //winston.info('Incoming message from MQTT: %s = %s', topic, contents); // Remove the preface from the topic before splitting it var pieces = topic.substr(config.mqtt.preface.length + 1).split('/'), device = pieces[0], property = pieces[1], topicReadState = getTopicFor(device, property, TOPIC_READ_STATE), - topicWriteState = getTopicFor(device, property, TOPIC_WRITE_STATE), topicSwitchState = getTopicFor(device, 'switch', TOPIC_READ_STATE), topicLevelCommand = getTopicFor(device, 'level', TOPIC_COMMAND); @@ -270,6 +274,7 @@ function parseMQTTMessage (topic, message) { } } history[topic] = contents; + winston.info('Incoming message from MQTT: %s = %s', topic, contents); // If sending level data and the switch is off, don't send anything // SmartThings will turn the device on (which is confusing) @@ -287,24 +292,38 @@ function parseMQTTMessage (topic, message) { contents = history[topicLevelCommand]; } - request.post({ + var json = { + name: device, + type: property, + value: contents, + //command: (!pieces[2] || pieces[2] && pieces[2] === config.mqtt[SUFFIX_COMMAND]) + command: (pieces[2] && pieces[2] === config.mqtt[SUFFIX_COMMAND]) || false + }; + + retryPost(json, true); +} + +function retryPost(json, retry) { + request.post({ url: 'http://' + callback, - json: { - name: device, - type: property, - value: contents, - command: (!pieces[2] || pieces[2] && pieces[2] === config.mqtt[SUFFIX_COMMAND]) - } + json: json }, function (error, resp) { if (error) { - // @TODO handle the response from SmartThings - winston.error('Error from SmartThings Hub: %s', error.toString()); - winston.error(JSON.stringify(error, null, 4)); - winston.error(JSON.stringify(resp, null, 4)); + if ((error.code === "ETIMEDOUT") && retry) { + winston.error('Error connecting SmartThings, retrying'); + retryPost(json); + } + else { + // @TODO handle the response from SmartThings + winston.error('Error from SmartThings Hub: %s', error.toString()); + winston.error(JSON.stringify(error, null, 4)); + winston.error(JSON.stringify(resp, null, 4)); + } } }); } + // Main flow async.series([ function loadFromDisk (next) { diff --git a/smartapps/stj/mqtt-bridge.src/mqtt-bridge.groovy b/smartapps/stj/mqtt-bridge.src/mqtt-bridge.groovy index 2c13d2d..39a5da1 100644 --- a/smartapps/stj/mqtt-bridge.src/mqtt-bridge.groovy +++ b/smartapps/stj/mqtt-bridge.src/mqtt-bridge.groovy @@ -4,6 +4,7 @@ * Authors * - st.john.johnson@gmail.com * - jeremiah.wuenschel@gmail.com + * - Indu Prakash * * Copyright 2016 * @@ -44,13 +45,7 @@ import groovy.transform.Field "battery" ] ], - "beacon": [ - name: "Beacon", - capability: "capability.beacon", - attributes: [ - "presence" - ] - ], + "button": [ name: "Button", capability: "capability.button", @@ -58,38 +53,7 @@ import groovy.transform.Field "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", @@ -103,13 +67,14 @@ import groovy.transform.Field capability: "capability.contactSensor", attributes: [ "contact" - ] + ], + action: "actionOpenClosed" ], "doorControl": [ name: "Door Control", capability: "capability.doorControl", attributes: [ - "door" + "door", "notify" ], action: "actionOpenClosed" ], @@ -120,21 +85,14 @@ import groovy.transform.Field "energy" ] ], - "garageDoors": [ + /*"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", @@ -150,22 +108,7 @@ import groovy.transform.Field ], 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", @@ -174,25 +117,6 @@ import groovy.transform.Field ], 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", @@ -230,43 +154,7 @@ import groovy.transform.Field "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", @@ -275,13 +163,7 @@ import groovy.transform.Field ], action: "actionOnOff" ], - "soundPressureLevel": [ - name: "Sound Pressure Level", - capability: "capability.soundPressureLevel", - attributes: [ - "soundPressureLevel" - ] - ], + "tamperAlert": [ name: "Tamper Alert", capability: "capability.tamperAlert", @@ -363,15 +245,6 @@ import groovy.transform.Field "threeAxis" ] ], - "timedSession": [ - name: "Timed Session", - capability: "capability.timedSession", - attributes: [ - "timeRemaining", - "sessionStatus" - ], - action: "actionTimedSession" - ], "touchSensor": [ name: "Touch Sensor", capability: "capability.touchSensor", @@ -379,14 +252,7 @@ import groovy.transform.Field "touch" ] ], - "valve": [ - name: "Valve", - capability: "capability.valve", - attributes: [ - "contact" - ], - action: "actionOpenClosed" - ], + "voltageMeasurement": [ name: "Voltage Measurement", capability: "capability.voltageMeasurement", @@ -400,14 +266,6 @@ import groovy.transform.Field attributes: [ "water" ] - ], - "windowShades": [ - name: "Window Shade", - capability: "capability.windowShade", - attributes: [ - "windowShade" - ], - action: "actionOpenClosed" ] ] @@ -476,6 +334,9 @@ def initialize() { // Update the bridge updateSubscription() + + sendBatteryStatuses() + runIn(5, sendBatteryStatuses) } // Update the bridge"s subscription @@ -513,10 +374,11 @@ def bridgeHandler(evt) { if (json.type == "notify") { if (json.name == "Contacts") { sendNotificationToContacts("${json.value}", recipients) - } else { + return + } else if (json.name == "System") { sendNotificationEvent("${json.value}") + return } - return } // @NOTE this is stored AWFUL, we need a faster lookup table @@ -526,11 +388,18 @@ def bridgeHandler(evt) { settings[key].each {device -> if (device.displayName == json.name) { if (json.command == false) { + //setStatus is exposed by Espurna Garage Door Device Handler V2 + //invoke action as fallback if setStatus is absent if (device.getSupportedCommands().any {it.name == "setStatus"}) { log.debug "Setting state ${json.type} = ${json.value}" device.setStatus(json.type, json.value) state.ignoreEvent = json; } + else if (capability.containsKey("action")) { + def action = capability["action"] + // Yes, this is calling the method dynamically + "$action"(device, json.type, json.value) + } } else { if (capability.containsKey("action")) { @@ -571,6 +440,30 @@ def inputHandler(evt) { } } + +def sendBatteryStatuses() { + //https://docs.smartthings.com/en/latest/ref-docs/device-ref.html + def devicesWithBattery = settings["battery"] + log.debug "sendBatteryStatuses ${devicesWithBattery}" + devicesWithBattery.each{device-> + sendBatteryStatus(device) + } +} + +def sendBatteryStatus(device) { + def json = new JsonOutput().toJson([ + path: "/push", + body: [ + name: device.displayName, + value: device.currentState("battery"), + type: "battery" + ] + ]) + + log.debug "sendBatteryStatus: ${json}" + bridge.deviceNotification(json) +} + // +---------------------------------+ // | WARNING, BEYOND HERE BE DRAGONS | // +---------------------------------+ @@ -595,27 +488,15 @@ def actionAlarm(device, attribute, value) { } } -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() + if (device.hasCommand("open")) { + device.open() + } } else if (value == "closed") { - device.close() + if (device.hasCommand("close")) { + device.close() + } } } @@ -635,47 +516,6 @@ def actionActiveInactive(device, attribute, value) { } } -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) } @@ -691,34 +531,4 @@ def actionPresence(device, attribute, value) { 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) - } -} +} \ No newline at end of file