diff --git a/devicetypes/alarmdecoder/alarmdecoder-network-appliance.src/alarmdecoder-network-appliance.groovy b/devicetypes/alarmdecoder/alarmdecoder-network-appliance.src/alarmdecoder-network-appliance.groovy index 35df681..87cfaaa 100644 --- a/devicetypes/alarmdecoder/alarmdecoder-network-appliance.src/alarmdecoder-network-appliance.groovy +++ b/devicetypes/alarmdecoder/alarmdecoder-network-appliance.src/alarmdecoder-network-appliance.groovy @@ -83,6 +83,8 @@ metadata { "armed", "armed_stay", "armed_stay_exit", + "armed_night", + "armed_night_exit", "disarmed", "alarming", "fire", @@ -171,6 +173,16 @@ metadata { "armed_stay_exit", label: 'Armed (exit-now)', icon: "st.nest.nest-away", + backgroundColor: "#ffa81e") + attributeState( + "armed_night", + label: 'Armed (night)', + icon: "st.security.alarm.on", + backgroundColor: "#ffa81e") + attributeState( + "armed_night_exit", + label: 'Armed (exit-now)', + icon: "st.nest.nest-away", backgroundColor: "#ffa81e") attributeState( "disarmed", @@ -227,6 +239,16 @@ metadata { action: "disarm", icon: "st.security.alarm.off", label: "DISARM") + state( + "armed_night", + action: "disarm", + icon: "st.security.alarm.off", + label: "DISARM") + state( + "armed_night_exit", + action: "disarm", + icon: "st.security.alarm.off", + label: "DISARM") state( "disarmed", action: "arm_away", @@ -280,6 +302,16 @@ metadata { action: "disarm", icon: "st.security.alarm.off", label: "DISARM") + state( + "armed_night", + action: "exit", + icon: "st.nest.nest-away", + label: "EXIT") + state( + "armed_night_exit", + action: "disarm", + icon: "st.security.alarm.off", + label: "DISARM") state( "disarmed", action: "arm_stay", @@ -658,7 +690,7 @@ def installed() { * Called when the device page reports an uninstall event. */ def uninstalled() { - log.trace "--- handler.uninstalled" + parent.logTrace "--- handler.uninstalled" } /** @@ -667,7 +699,7 @@ def uninstalled() { * Called when the device settings are udpated. */ def updated() { - log.trace "--- handler.updated" + parent.logTrace "--- handler.updated" state.faulted_zones = [] @@ -675,6 +707,7 @@ def updated() { state.panel_ready = true state.panel_armed = false state.panel_armed_stay = false + state.panel_armed_night = false state.panel_exit = false state.panel_fire_detected = false state.panel_alarming = false @@ -720,8 +753,7 @@ def updated() { * */ def parse(String description) { - if (parent.debug) - log.debug("--- parse: em: ${em}, description: '${description}'") + parent.logDebug("--- parse: em: ${em}, description: '${description}'") // Create our events array we will append into. def events = [] @@ -752,17 +784,15 @@ def parse(String description) { return events } - log.info("--- parse: mac: ${event?.mac} requestId: ${rID}") + parent.logTrace("--- parse: mac: ${event?.mac} requestId: ${rID}") // The body may be empty. def bodyString = (em.body) ? em.body : "" // Verbose debug details. - if (parent.debug) { - log.debug("--- parse: type: ${em.contenttype} " + - "rid: ${em.requestId}, headers: ${em.headers}") - log.debug("--- parse: rid: ${rID}, body: ${em.body}") - } + parent.logDebug("--- parse: type: ${em.contenttype} " + + "rid: ${em.requestId}, headers: ${em.headers}") + parent.logDebug("--- parse: rid: ${rID}, body: ${em.body}") def type = em.contenttype @@ -776,11 +806,10 @@ def parse(String description) { e-> events << e } } else { - if (parent.debug) log.debug("--- parse: unknown type: ${type}") + parent.logDebug("--- parse: unknown type: ${type}") } - if (parent.debug) - log.debug("--- parse: results rid:${rID}, events: ${events}") + parent.logDebug("--- parse: results rid:${rID}, events: ${events}") return events } @@ -790,7 +819,7 @@ def parse(String description) { /*** Capabilities ***/ def refresh() { - log.trace("--- handler.refresh") + parent.logTrace("--- handler.refresh") def urn = getDataValue("urn") def apikey = _get_api_key() @@ -805,7 +834,7 @@ def refresh() { * TODO: Add security */ def disarm() { - log.trace("--- disarm") + parent.logTrace("--- disarm") def user_code = _get_user_code() def keys = "" @@ -826,7 +855,7 @@ def disarm() { * TODO: Add security */ def exit() { - log.trace("--- exit") + parent.logTrace("--- exit") def user_code = _get_user_code() def keys = "" @@ -846,7 +875,7 @@ def exit() { * Sends an arm away command to the panel */ def arm_away() { - log.trace("--- arm_away") + parent.logTrace("--- arm_away") def user_code = _get_user_code() def keys = "" @@ -866,7 +895,7 @@ def arm_away() { * Sends an arm stay command to the panel */ def arm_stay() { - log.trace("--- arm_stay") + parent.logTrace("--- arm_stay") def user_code = _get_user_code() def keys = "" @@ -881,12 +910,29 @@ def arm_stay() { return send_keys(keys) } +/** + * arm_night() + * Sends an arm night command to the panel + */ +def arm_night() { + parent.logTrace("--- arm_night") + if (settings.panel_type == "ADEMCO") { + return send_keys("${user_code}33") + } + else if (settings.panel_type == "DSC") { + arm_stay() + return send_keys("*1") + } + else + log.warn("--- arm_night: unknown panel_type.") +} + /** * fire() * Sends an fire alarm command to the panel */ def fire() { - log.trace("--- fire") + parent.logTrace("--- fire") state.fire_started = null def keys = "" return send_keys(keys) @@ -897,7 +943,7 @@ def fire1() { runIn(10, checkFire); - log.trace("Fire stage 1: ${state.fire_started}") + parent.logTrace("Fire stage 1: ${state.fire_started}") } def fire2() { @@ -905,14 +951,14 @@ def fire2() { runIn(10, checkFire); - log.trace("Fire stage 2: ${state.fire_started}") + parent.logTrace("Fire stage 2: ${state.fire_started}") } def checkFire() { - log.trace("checkFire"); + parent.logTrace("checkFire"); if (state.fire_started != null && new Date().time - state.fire_started >= 5) { - sendEvent(name: "fire_state", value: "default", isStateChange: true); - log.trace("clearing fire"); + sendEvent(name: "fire_state", value: "default", isStateChange: true) + parent.logTrace("clearing fire") } } @@ -921,7 +967,7 @@ def checkFire() { * Sends an panic alarm command to the panel */ def panic() { - log.trace("--- panic") + parent.logTrace("--- panic") state.panic_started = null def keys = "" return send_keys(keys) @@ -932,7 +978,7 @@ def panic1() { runIn(10, checkPanic); - log.trace("Panic stage 1: ${state.panic_started}") + parent.logTrace("Panic stage 1: ${state.panic_started}") } def panic2() { @@ -940,15 +986,15 @@ def panic2() { runIn(10, checkPanic); - log.trace("Panic stage 2: ${state.panic_started}") + parent.logTrace("Panic stage 2: ${state.panic_started}") } def checkPanic() { - log.trace("checkPanic"); + parent.logTrace("checkPanic"); if (state.panic_started != null && new Date().time - state.panic_started >= 5) { sendEvent(name: "panic_state", value: "default", isStateChange: true); - log.trace("clearing panic"); + parent.logTrace("clearing panic"); } } @@ -957,7 +1003,7 @@ def checkPanic() { * Sends an aux alarm command to the panel */ def aux() { - log.trace("--- aux") + parent.logTrace("--- aux") state.aux_started = null def keys = "" return send_keys(keys) @@ -968,7 +1014,7 @@ def aux1() { runIn(10, checkAux); - log.trace("Aux stage 1: ${state.aux_started}") + parent.logTrace("Aux stage 1: ${state.aux_started}") } def aux2() { @@ -976,14 +1022,14 @@ def aux2() { runIn(10, checkAux); - log.trace("Aux stage 2: ${state.aux_started}") + parent.logTrace("Aux stage 2: ${state.aux_started}") } def checkAux() { - log.trace("checkAux"); + parent.logTrace("checkAux"); if (state.aux_started != null && new Date().time - state.aux_started >= 5) { sendEvent(name: "aux_state", value: "default", isStateChange: true); - log.trace("clearing aux"); + parent.logTrace("clearing aux"); } } @@ -1018,7 +1064,7 @@ def bypassN(szValue) { * Send a bypass command to the panel for a zone number. */ def bypass(zone) { - log.trace("--- bypass ${zone}") + parent.logTrace("--- bypass ${zone}") // if no zone then skip if (!zone.toInteger()) @@ -1042,7 +1088,7 @@ def bypass(zone) { * Sends a chime command to the panel */ def chime() { - log.trace("--- chime") + parent.logTrace("--- chime") def user_code = _get_user_code() def keys = "" @@ -1063,7 +1109,7 @@ def chime() { * process a state change event object from xml/json parser */ def update_state(data) { - log.trace("--- update_state") + parent.logTrace("--- update_state") def forceguiUpdate = false def skipstate = false def events = [] @@ -1167,11 +1213,16 @@ def update_state(data) { if (data.panel_exit) { if (data.panel_armed_stay) { panel_state = "armed_stay_exit" + if (data.panel_entry_delay_off == false && data.panel_perimeter_only == true) + panel_state = "armed_night_exit" } else { panel_state = "armed_exit" } } else { - panel_state = (data.panel_armed_stay ? "armed_stay" : "armed") + if (data.panel_armed_stay && data.panel_entry_delay_off == false && data.panel_perimeter_only == true) + panel_state = "armed_night" + else + panel_state = (data.panel_armed_stay ? "armed_stay" : "armed") } } @@ -1196,18 +1247,45 @@ def update_state(data) { isStateChange: true) // If armed STAY changes data.panel_armed_stay - if (forceguiUpdate || data.panel_armed_stay != state.panel_armed_stay) { + if (forceguiUpdate || data.panel_armed_stay != state.panel_armed_stay) { if (data.panel_armed_stay) { - events << createEvent( - name: "arm-stay-set", - value: "on", - displayed: true, - isStateChange: true) - events << createEvent( - name: "arm-away-set", - value: "off", - displayed: true, - isStateChange: true) + + if (data.panel_entry_delay_off == false && data.panel_perimeter_only) + { + events << createEvent( + name: "arm-stay-set", + value: "off", + displayed: true, + isStateChange: true) + events << createEvent( + name: "arm-away-set", + value: "off", + displayed: true, + isStateChange: true) + events << createEvent( + name: "arm-night-set", + value: "on", + displayed: true, + isStateChange: true) + } + else + { + events << createEvent( + name: "arm-stay-set", + value: "on", + displayed: true, + isStateChange: true) + events << createEvent( + name: "arm-away-set", + value: "off", + displayed: true, + isStateChange: true) + events << createEvent( + name: "arm-night-set", + value: "off", + displayed: true, + isStateChange: true) + } } } @@ -1223,6 +1301,11 @@ def update_state(data) { name: "arm-stay-set", value: "off", displayed: true, + isStateChange: true) + events << createEvent( + name: "arm-night-set", + value: "off", + displayed: true, isStateChange: true) events << createEvent( name: "disarm-set", @@ -1276,7 +1359,7 @@ def update_state(data) { // set our panel_state if (forceguiUpdate || panel_state != state.panel_state) { - log.trace("--- update_state: new state **** ${panel_state} ****") + parent.logTrace("--- update_state: new state **** ${panel_state} ****") events << createEvent( name: "panel_state", value: panel_state, @@ -1289,7 +1372,11 @@ def update_state(data) { if (armed) { alarm_status = "away" if (data.panel_armed_stay == true) + { alarm_status = "stay" + if (!data.panel_entry_delay_off && data.panel_perimeter_only) + alarm_status = "night" + } } // Create an event to notify Smart Home Monitor in our service. @@ -1334,6 +1421,7 @@ def update_state(data) { state.panel_chime = data.chime state.panel_perimeter_only = data.panel_perimeter_only state.panel_entry_delay_off = data.panel_entry_delay_off + state.panel_armed_night = data.panel_armed_stay && data.panel_perimeter_only && !data.panel_entry_delay_off } return events } @@ -1359,8 +1447,8 @@ def parse_json(String body) { e-> events << e } - if (parent.debug) log.debug("parse_json in:****** ${resultMap}") - if (parent.debug) log.debug("parse_json out:****** ${events}") + parent.logDebug("parse_json in:****** ${resultMap}") + parent.logDebug("parse_json out:****** ${events}") } catch (Exception e) { log.error("parse_json: Exception ${e}") @@ -1448,8 +1536,8 @@ def parse_xml(String body) { e-> events << e } - if (parent.debug) log.debug("parse_xml in:****** ${resultMap}") - if (parent.debug) log.debug("parse_xml out:****** ${events}") + parent.logDebug("parse_xml in:****** ${resultMap}") + parent.logDebug("parse_xml out:****** ${events}") } catch (Exception e) { log.error("parse_xml: Exception ${e}") @@ -1471,8 +1559,7 @@ def parse_xml(String body) { * ssdpPath: /static/device_description.xml */ def subscribeNotifications() { - if (parent.debug) - log.trace "--- subscribeNotifications: ${getDataValue("urn")}" + parent.logTrace "--- subscribeNotifications: ${getDataValue("urn")}" // Get our HUBs address details for callbacks. def address = parent.getHubURN() @@ -1525,22 +1612,20 @@ private def build_zone_events(data) { def new_faults = current_faults.minus(state.faulted_zones) def cleared_faults = state.faulted_zones.minus(current_faults) - if (parent.debug) { - log.trace("Current faulted zones: ${current_faults}") - log.trace("New faults: ${new_faults}") - log.trace("Cleared faults: ${cleared_faults}") - } + parent.logTrace("Current faulted zones: ${current_faults}") + parent.logTrace("New faults: ${new_faults}") + parent.logTrace("Cleared faults: ${cleared_faults}") // Trigger switches for newly faulted zones. for (def i = 0; i < new_faults.size(); i++) { - if (parent.debug) log.trace("Setting switch ${new_faults[i]}") + parent.logTrace("Setting switch ${new_faults[i]}") def switch_events = update_zone_switch(new_faults[i], true) events = events.plus(switch_events) } // Reset switches for cleared zones. for (def i = 0; i < cleared_faults.size(); i++) { - if (parent.debug) log.trace("Clearing switch ${cleared_faults[i]}") + parent.logTrace("Clearing switch ${cleared_faults[i]}") def switch_events = update_zone_switch(cleared_faults[i], false) events = events.plus(switch_events) } @@ -1606,10 +1691,7 @@ private def update_zone_switch(zone, faulted) { * AlarmDecoder REST api send command. */ def send_keys(String keys) { - if (parent.debug) - log.trace("--- send_keys: keys=${keys}") - else - log.trace("--- send_keys") + parent.logTrace("--- send_keys: keys=${keys}") def urn = getDataValue("urn") def apikey = _get_api_key() @@ -1627,8 +1709,7 @@ def send_keys(String keys) { * Build a GET request HubAction object. */ def hub_http_get(host, path) { - if (parent.debug) - log.trace "--- hub_http_get: host=${host}, path=${path}" + parent.logTrace "--- hub_http_get: host=${host}, path=${path}" def httpRequest = [ method: "GET", @@ -1645,8 +1726,7 @@ def hub_http_get(host, path) { * Build a POST request HubAction object. */ def hub_http_post(host, path, body) { - if (parent.debug) - log.trace "--- hub_http_post: host=${host}, path=${path} body=${body}" + parent.logTrace "--- hub_http_post: host=${host}, path=${path} body=${body}" def httpRequest = [ method: "POST", @@ -1685,4 +1765,4 @@ def _get_api_key() { */ def getStateValue(key) { return state[key] -} +} \ No newline at end of file diff --git a/devicetypes/alarmdecoder/alarmdecoder-virtual-carbon-monoxide-detector.src/alarmdecoder-virtual-carbon-monoxide-detector.groovy b/devicetypes/alarmdecoder/alarmdecoder-virtual-carbon-monoxide-detector.src/alarmdecoder-virtual-carbon-monoxide-detector.groovy index d7104d8..d68a447 100644 --- a/devicetypes/alarmdecoder/alarmdecoder-virtual-carbon-monoxide-detector.src/alarmdecoder-virtual-carbon-monoxide-detector.groovy +++ b/devicetypes/alarmdecoder/alarmdecoder-virtual-carbon-monoxide-detector.src/alarmdecoder-virtual-carbon-monoxide-detector.groovy @@ -28,6 +28,9 @@ metadata { namespace: APPNAMESPACE, author: "Nu Tech Software Solutions, Inc.") { capability "CarbonMonoxideDetector" + capability "Tamper Alert" + attribute "low_battery", "bool" + attribute "last_checkin", "number" } // tile definitions @@ -67,6 +70,26 @@ metadata { title: "Zone Number", description: "Zone # required for zone events.", required: false) + input( + name: "serial", type: + "string", title: "Serial Number", + description: "The serial number of an RF device.", + required: false) + if (serial != null) { + input( + name: "zoneLoop", type: + "number", title: "Zone Loop", + range: "1..4", + description: "The loop to use for zone open/close.", + required: true) + input( + name: "tamperLoop", type: + "number", title: "Tamper Loop", + range: "1..4", + description: "The loop to use to detect tamper.", + defaultValue: 4, + required: false) + } } } @@ -82,9 +105,15 @@ metadata { def installed() { updateDataValue("invert", invert.toString()) updateDataValue("zone", zone.toString()) + updateDataValue("serial", serial) + updateDataValue("zoneLoop", zoneLoop.toString()) + updateDataValue("tamperLoop", tamperLoop.toString()) } def updated() { updateDataValue("invert", invert.toString()) updateDataValue("zone", zone.toString()) + updateDataValue("serial", serial) + updateDataValue("zoneLoop", zoneLoop.toString()) + updateDataValue("tamperLoop", tamperLoop.toString()) } diff --git a/devicetypes/alarmdecoder/alarmdecoder-virtual-contact-sensor.src/alarmdecoder-virtual-contact-sensor.groovy b/devicetypes/alarmdecoder/alarmdecoder-virtual-contact-sensor.src/alarmdecoder-virtual-contact-sensor.groovy index 0167270..6fd0172 100644 --- a/devicetypes/alarmdecoder/alarmdecoder-virtual-contact-sensor.src/alarmdecoder-virtual-contact-sensor.groovy +++ b/devicetypes/alarmdecoder/alarmdecoder-virtual-contact-sensor.src/alarmdecoder-virtual-contact-sensor.groovy @@ -28,6 +28,9 @@ metadata { namespace: APPNAMESPACE, author: "Nu Tech Software Solutions, Inc.") { capability "Contact Sensor" + capability "Tamper Alert" + attribute "low_battery", "bool" + attribute "last_checkin", "number" } // tile definitions @@ -66,6 +69,27 @@ metadata { "number", title: "Zone Number", description: "Zone # required for zone events.", required: false) + input( + name: "serial", type: + "string", title: "Serial Number", + submitOnChange: true, + description: "The serial number of an RF device.", + required: false) + if (serial != null) { + input( + name: "zoneLoop", type: + "number", title: "Zone Loop", + range: "1..4", + description: "The loop to use for zone open/close.", + required: true) + input( + name: "tamperLoop", type: + "number", title: "Tamper Loop", + range: "1..4", + description: "The loop to use to detect tamper.", + defaultValue: 4, + required: false) + } } } @@ -81,9 +105,15 @@ metadata { def installed() { updateDataValue("invert", invert.toString()) updateDataValue("zone", zone.toString()) + updateDataValue("serial", serial) + updateDataValue("zoneLoop", zoneLoop.toString()) + updateDataValue("tamperLoop", tamperLoop.toString()) } def updated() { updateDataValue("invert", invert.toString()) updateDataValue("zone", zone.toString()) + updateDataValue("serial", serial) + updateDataValue("zoneLoop", zoneLoop.toString()) + updateDataValue("tamperLoop", tamperLoop.toString()) } diff --git a/devicetypes/alarmdecoder/alarmdecoder-virtual-motion-detector.src/alarmdecoder-virtual-motion-detector.groovy b/devicetypes/alarmdecoder/alarmdecoder-virtual-motion-detector.src/alarmdecoder-virtual-motion-detector.groovy index c7e948d..5fa1e01 100644 --- a/devicetypes/alarmdecoder/alarmdecoder-virtual-motion-detector.src/alarmdecoder-virtual-motion-detector.groovy +++ b/devicetypes/alarmdecoder/alarmdecoder-virtual-motion-detector.src/alarmdecoder-virtual-motion-detector.groovy @@ -28,6 +28,9 @@ metadata { namespace: APPNAMESPACE, author: "Nu Tech Software Solutions, Inc.") { capability "Motion Sensor" + capability "Tamper Alert" + attribute "low_battery", "bool" + attribute "last_checkin", "number" } // tile definitions @@ -69,6 +72,26 @@ metadata { title: "Zone Number", description: "Zone # required for zone events.", required: false) + input( + name: "serial", type: + "string", title: "Serial Number", + description: "The serial number of an RF device.", + required: false) + if (serial != null) { + input( + name: "zoneLoop", type: + "number", title: "Zone Loop", + range: "1..4", + description: "The loop to use for zone open/close.", + required: true) + input( + name: "tamperLoop", type: + "number", title: "Tamper Loop", + range: "1..4", + description: "The loop to use to detect tamper.", + defaultValue: 4, + required: false) + } } } @@ -84,11 +107,17 @@ metadata { def installed() { updateDataValue("invert", invert.toString()) updateDataValue("zone", zone.toString()) + updateDataValue("serial", serial) + updateDataValue("zoneLoop", zoneLoop.toString()) + updateDataValue("tamperLoop", tamperLoop.toString()) } def updated() { updateDataValue("invert", invert.toString()) updateDataValue("zone", zone.toString()) + updateDataValue("serial", serial) + updateDataValue("zoneLoop", zoneLoop.toString()) + updateDataValue("tamperLoop", tamperLoop.toString()) } // FIXME: what? diff --git a/devicetypes/alarmdecoder/alarmdecoder-virtual-shock-sensor.src/alarmdecoder-virtual-shock-sensor.groovy b/devicetypes/alarmdecoder/alarmdecoder-virtual-shock-sensor.src/alarmdecoder-virtual-shock-sensor.groovy index f8c838e..97e0527 100644 --- a/devicetypes/alarmdecoder/alarmdecoder-virtual-shock-sensor.src/alarmdecoder-virtual-shock-sensor.groovy +++ b/devicetypes/alarmdecoder/alarmdecoder-virtual-shock-sensor.src/alarmdecoder-virtual-shock-sensor.groovy @@ -28,6 +28,9 @@ metadata { namespace: APPNAMESPACE, author: "Nu Tech Software Solutions, Inc.") { capability "ShockSensor" + capability "Tamper Alert" + attribute "low_battery", "bool" + attribute "last_checkin", "number" } // tile definitions @@ -68,6 +71,26 @@ metadata { title: "Zone Number", description: "Zone # required for zone events.", required: false) + input( + name: "serial", type: + "string", title: "Serial Number", + description: "The serial number of an RF device.", + required: false) + if (serial != null) { + input( + name: "zoneLoop", type: + "number", title: "Zone Loop", + range: "1..4", + description: "The loop to use for zone open/close.", + required: true) + input( + name: "tamperLoop", type: + "number", title: "Tamper Loop", + range: "1..4", + description: "The loop to use to detect tamper.", + defaultValue: 4, + required: false) + } } } @@ -83,9 +106,15 @@ metadata { def installed() { updateDataValue("invert", invert.toString()) updateDataValue("zone", zone.toString()) + updateDataValue("serial", serial) + updateDataValue("zoneLoop", zoneLoop.toString()) + updateDataValue("tamperLoop", tamperLoop.toString()) } def updated() { updateDataValue("invert", invert.toString()) updateDataValue("zone", zone.toString()) + updateDataValue("serial", serial) + updateDataValue("zoneLoop", zoneLoop.toString()) + updateDataValue("tamperLoop", tamperLoop.toString()) } diff --git a/devicetypes/alarmdecoder/alarmdecoder-virtual-smoke-alarm.src/alarmdecoder-virtual-smoke-alarm.groovy b/devicetypes/alarmdecoder/alarmdecoder-virtual-smoke-alarm.src/alarmdecoder-virtual-smoke-alarm.groovy index a0a6ffe..a05c60c 100644 --- a/devicetypes/alarmdecoder/alarmdecoder-virtual-smoke-alarm.src/alarmdecoder-virtual-smoke-alarm.groovy +++ b/devicetypes/alarmdecoder/alarmdecoder-virtual-smoke-alarm.src/alarmdecoder-virtual-smoke-alarm.groovy @@ -28,6 +28,9 @@ metadata { namespace: APPNAMESPACE, author: "Nu Tech Software Solutions, Inc.") { capability "Smoke Detector" + capability "Tamper Alert" + attribute "low_battery", "bool" + attribute "last_checkin", "number" } // tile definitions @@ -67,6 +70,26 @@ metadata { title: "Zone Number", description: "Zone # required for zone events.", required: false) + input( + name: "serial", type: + "string", title: "Serial Number", + description: "The serial number of an RF device.", + required: false) + if (serial != null) { + input( + name: "zoneLoop", type: + "number", title: "Zone Loop", + range: "1..4", + description: "The loop to use for zone open/close.", + required: true) + input( + name: "tamperLoop", type: + "number", title: "Tamper Loop", + range: "1..4", + description: "The loop to use to detect tamper.", + defaultValue: 4, + required: false) + } } } @@ -82,9 +105,15 @@ metadata { def installed() { updateDataValue("invert", invert.toString()) updateDataValue("zone", zone.toString()) + updateDataValue("serial", serial) + updateDataValue("zoneLoop", zoneLoop.toString()) + updateDataValue("tamperLoop", tamperLoop.toString()) } def updated() { updateDataValue("invert", invert.toString()) updateDataValue("zone", zone.toString()) + updateDataValue("serial", serial) + updateDataValue("zoneLoop", zoneLoop.toString()) + updateDataValue("tamperLoop", tamperLoop.toString()) } diff --git a/smartapps/alarmdecoder/alarmdecoder-service.src/alarmdecoder-service.groovy b/smartapps/alarmdecoder/alarmdecoder-service.src/alarmdecoder-service.groovy index 1e4a9cd..203ae72 100644 --- a/smartapps/alarmdecoder/alarmdecoder-service.src/alarmdecoder-service.groovy +++ b/smartapps/alarmdecoder/alarmdecoder-service.src/alarmdecoder-service.groovy @@ -67,8 +67,6 @@ import groovy.transform.Field /** * System Settings */ -@Field debug = false -@Field MAX_VIRTUAL_ZONES = 20 @Field NOCREATEDEV = false @Field CREATE_DISARM = true @@ -241,6 +239,8 @@ def szt(String name, Object... args) { // Main Page "page_main_title": "Setup And Management", "page_main_device_found": "${lname} service found.\nSelect from management options below.", + "debug_logging": "Enable debug logging", + "trace_logging": "Enable trace logging", // Discover/Install "page_discover_title": "Install Service", @@ -425,6 +425,9 @@ def page_main() { ) } } else { + section("") { + input(name: "deviceCount", type: "number", title: "How many child devices should be created?", required: true, defaultValue: 20 ) + } section(foundMsg) { href( name: "href_cid_management", @@ -471,6 +474,10 @@ def page_main() { ) } } + section("") { + input("debugOutput", "bool", title: szt("debug_logging"), submitOnChange: true) + input("traceOutput", "bool", title: szt("trace_logging"), submitOnChange: true) + } } } @@ -542,7 +549,7 @@ def page_remove_selected_cid() { it == device_name } if (d) { - log.trace("removing CID device ${device.deviceNetworkId}") + logTrace("removing CID device ${device.deviceNetworkId}") try { deleteChildDevice(device.deviceNetworkId) input_cid_devices.remove(device_name) @@ -838,7 +845,7 @@ def page_remove_selected_rfx() { it == device_name } if (d) { - log.trace("removing RFX device ${device.deviceNetworkId}") + logTrace("removing RFX device ${device.deviceNetworkId}") try { deleteChildDevice(device.deviceNetworkId) input_rfx_devices.remove(device_name) @@ -1140,7 +1147,7 @@ def page_select_device() { // build list of currently known AlarmDecoder parent devices def found_devices = [: ] def options = getDevices().each { k, v -> - if (debug) log.debug "page_select: ${v}" + logDebug "page_select: ${v}" def ip = convertHexToIP(v.ip) found_devices["${v.ip}:${v.port}"] = "AlarmDecoder @ ${ip}" } @@ -1179,9 +1186,9 @@ def page_discover() { // build list of currently known AlarmDecoder parent devices def found_devices = [: ] - log.debug "devices ${getDevices()}" + logDebug "devices ${getDevices()}" def options = getDevices().each { k, v -> - if (debug) log.debug "page_discover: ${v}" + logDebug "page_discover: ${v}" def ip = convertHexToIP(v.ip) found_devices["${v.ip}:${v.port}"] = "AlarmDecoder @ ${ip}" } @@ -1440,8 +1447,8 @@ def page_relink_update(params) { * installed() */ def installed() { - log.trace "installed" - if (debug) log.debug "Installed with settings: ${settings}" + logTrace "installed" + logDebug "Installed with settings: ${settings}" // initialize everything initialize() @@ -1451,8 +1458,8 @@ def installed() { * updated() */ def updated() { - log.trace "updated" - if (debug) log.debug "Updated with settings: ${settings}" + logTrace "updated" + logDebug "Updated with settings: ${settings}" // re initialize everything initialize() @@ -1462,7 +1469,7 @@ def updated() { * uninstalled() */ def uninstalled() { - log.trace "uninstalled" + logTrace "uninstalled" // disable all scheduling and subscriptions unschedule() @@ -1471,10 +1478,10 @@ def uninstalled() { def devices = getAllChildDevices() devices.each { try { - log.debug "deleting child device: ${it.deviceNetworkId}" + logDebug "deleting child device: ${it.deviceNetworkId}" deleteChildDevice(it.deviceNetworkId) } catch (Exception e) { - log.trace("exception while uninstalling: ${e}") + log.error("exception while uninstalling: ${e}") } } } @@ -1485,7 +1492,7 @@ def uninstalled() { * Create our default state */ def initialize() { - log.trace "initialize" + logTrace "initialize" // unsubscribe from everything unsubscribe() @@ -1514,8 +1521,7 @@ def initialize() { // Only refresh the main device that has a panel_state def device_type = device.getTypeName() if (device_type == "AlarmDecoder network appliance") { - if (debug) - log.debug("initialize: Found device refresh subscription.") + logDebug("initialize: Found device refresh subscription.") device.subscribeNotifications() } } @@ -1535,13 +1541,11 @@ def initialize() { * */ def locationHandler(evt) { - if (debug) - log.trace "locationHandler: name: '${evt.name}'" + logTrace "locationHandler: name: '${evt.name}'" // only process events with a description. if (!evt.description) { - if (debug) - log.info("locationHandler: skipping event missing 'description'") + logDebug("locationHandler: skipping event missing 'description'") return } @@ -1553,8 +1557,7 @@ def locationHandler(evt) { if (parsedEvent.ssdpTerm?.contains(SSDPTERM)) { def ct = now() - if (debug) - log.debug "locationHandler: received ssdpTerm match." + logDebug "locationHandler: received ssdpTerm match." // Pre fill parsed event object with hubId the event was from. parsedEvent << ["hubId": evt?.hubId] @@ -1594,17 +1597,15 @@ def locationHandler(evt) { // add/update the device in state.devices with local discovered devices. alarmdecoders << ["${parsedEvent.ssdpUSN.toString()}": parsedEvent] - if (debug) - log.debug("locationHandler: alarmdecoders found: ${alarmdecoders}") + logDebug("locationHandler: alarmdecoders found: ${alarmdecoders}") } else { // Content type already parsed here. def type = parsedEvent.contenttype - if (debug) - log.debug("locationHandler: HTTP request type:${type} " + - "body:${parsedEvent?.body} headers:${parsedEvent?.headers}") + logDebug("locationHandler: HTTP request type:${type} " + + "body:${parsedEvent?.body} headers:${parsedEvent?.headers}") // XML PUSH data if (type?.contains("xml")) { @@ -1612,8 +1613,7 @@ def locationHandler(evt) { def d = getChildDevice("${getDeviceKey()}") if (d) { if (d.getDeviceDataByName("mac") == parsedEvent.mac) { - if (debug) - log.debug("push_update_alarmdecoders: Found device parse xml data.") + logDebug("push_update_alarmdecoders: Found device parse xml data.") d.parse_xml(parsedEvent?.body).each { e-> d.sendEvent(e) @@ -1625,9 +1625,8 @@ def locationHandler(evt) { } // Unkonwn silently ignore - if (debug) - log.debug("locationHandler: ignoring unknown message from " + - "name:${evt.name} parsedEvent: ${parsedEvent}") + logDebug("locationHandler: ignoring unknown message from " + + "name:${evt.name} parsedEvent: ${parsedEvent}") } } @@ -1635,7 +1634,7 @@ def locationHandler(evt) { * Handle remote web requests for http://somegraph/update */ def webserviceUpdate() { - log.trace "webserviceUpdate" + logTrace "webserviceUpdate" refresh_alarmdecoders() return [status: "OK"] } @@ -1648,7 +1647,7 @@ def actionButton(id) { // grab our primary AlarmDecoder device object def d = getChildDevice("${getDeviceKey()}") - if (debug) log.debug("actionButton: desc=${id} dev=${d}") + logDebug("actionButton: desc=${id} dev=${d}") if (!d) { log.error("actionButton: Could not find primary dev. '${getDeviceKey()}'.") @@ -1705,7 +1704,7 @@ def actionButton(id) { * send event to smokeAlarm device to set state [detected, clear] */ def smokeSet(evt) { - if (debug) log.debug("smokeSet: desc=${evt.value}") + logDebug("smokeSet: desc=${evt.value}") def d = getChildDevices().find { it.deviceNetworkId.contains(":smokeAlarm") @@ -1729,7 +1728,7 @@ def smokeSet(evt) { * send event to armAway device to set state */ def armAwaySet(evt) { - if (debug) log.debug("armAwaySet ${evt.value}") + logDebug("armAwaySet ${evt.value}") def d = getChildDevice("${getDeviceKey()}:armAway") if (!d) { log.info("armAwaySet: Could not find 'armAway' device.") @@ -1749,7 +1748,7 @@ def armAwaySet(evt) { * send event to armStay device to set state */ def armStaySet(evt) { - if (debug) log.debug("armStaySet ${evt.value}") + logDebug("armStaySet ${evt.value}") def d = getChildDevice("${getDeviceKey()}:armStay") if (!d) { log.info("armStaySet: Could not find 'armStay' device.") @@ -1770,11 +1769,36 @@ def armStaySet(evt) { } } +/** + * send event to armNight device to set state + */ +def armNightSet(evt) { + logDebug("armStaySet ${evt.value}") + def d = getChildDevice("${getDeviceKey()}:armNight") + if (!d) { + log.info("armNightSet: Could not find 'armNight' device.") + } else { + d.sendEvent( + name: "switch", + value: evt.value, + isStateChange: true, + filtered: true + ) + } + + d = getChildDevice("${getDeviceKey()}:armNightStatus") + if (!d) { + log.info("armNightSet: Could not find 'armNightStatus' device.") + } else { + _sendEventTranslate(d, evt.value) + } +} + /** * send event to alarmbell indicator device to set state */ def alarmBellSet(evt) { - if (debug) log.debug("alarmBellSet ${evt.value}") + logDebug("alarmBellSet ${evt.value}") def d = getChildDevice("${getDeviceKey()}:alarmBell") if (!d) { log.info("alarmBellSet: Could not find 'alarmBell' device.") @@ -1794,8 +1818,7 @@ def alarmBellSet(evt) { * send event to chime indicator device to set state */ def chimeSet(evt) { - /* if (debug)*/ - log.debug("chimeSet ${evt.value}") + logDebug("chimeSet ${evt.value}") def d = getChildDevice("${getDeviceKey()}:chimeMode") if (!d) { log.info("chimeSet: Could not find device 'chimeMode'") @@ -1815,7 +1838,7 @@ def chimeSet(evt) { * send event to exit indicator device to set state */ def exitSet(evt) { - if (debug) log.debug("exitSet ${evt.value}") + logDebug("exitSet ${evt.value}") def d = getChildDevice("${getDeviceKey()}:exit") if (!d) { log.info("exitSet: Could not find device 'exit'") @@ -1835,7 +1858,7 @@ def exitSet(evt) { * send event to perimeter only indicator device to set state */ def perimeterOnlySet(evt) { - if (debug) log.debug("perimeterOnlySet ${evt.value}") + logDebug("perimeterOnlySet ${evt.value}") def d = getChildDevice("${getDeviceKey()}:perimeterOnlyStatus") if (!d) { log.info("perimeterOnlySet: Could not find device 'perimeterOnly'") @@ -1848,7 +1871,7 @@ def perimeterOnlySet(evt) { * send event to entry delay off indicator device to set state */ def entryDelayOffSet(evt) { - if (debug) log.debug("entryDelayOffSet ${evt.value}") + logDebug("entryDelayOffSet ${evt.value}") def d = getChildDevice("${getDeviceKey()}:entryDelayOffStatus") if (!d) { log.info("entryDelayOffSet: Could not find device 'entryDelayOff'") @@ -1862,7 +1885,7 @@ def entryDelayOffSet(evt) { * send event to bypass status device to set state */ def bypassSet(evt) { - if (debug) log.debug("bypassSet ${evt.value}") + logDebug("bypassSet ${evt.value}") def d = getChildDevice("${getDeviceKey()}:bypassStatus") if (!d) { log.info("bypassSet: Could not find device 'bypassStatus'") @@ -1875,7 +1898,7 @@ def bypassSet(evt) { * send event to ready status device to set state */ def readySet(evt) { - if (debug) log.debug("readySet ${evt.value}") + logDebug("readySet ${evt.value}") def d = getChildDevice("${getDeviceKey()}:readyStatus") if (!d) { log.info("readySet: Could not find 'readyStatus' device.") @@ -1888,7 +1911,7 @@ def readySet(evt) { * send event to disarm status device to set state */ def disarmSet(evt) { - if (debug) log.debug("disarmSet ${evt.value}") + logDebug("disarmSet ${evt.value}") def d = getChildDevice("${getDeviceKey()}:disarm") if (!d) { log.info("disarmSet: Could not find 'disarm' device.") @@ -1919,9 +1942,8 @@ def cidSet(evt) { // the partition # with 0 being system def partition = parts[1].toInteger() - if (debug) - log.debug("cidSet num:${cidnum} part: ${partition} " + - "state:${cidstate} val:${cidvalue}") + logDebug("cidSet num:${cidnum} part: ${partition} " + + "state:${cidstate} val:${cidvalue}") def sent = false def rawmsg = evt.value @@ -1936,16 +1958,14 @@ def cidSet(evt) { match = match.replace("?", ".") if (device_name =~ /${match}/) { - if (debug) - log.debug("cidSet device: ${device_name} matches ${match} " + - "sending state ${cidstate}") + logDebug("cidSet device: ${device_name} matches ${match} " + + "sending state ${cidstate}") _sendEventTranslate(it, cidstate) sent = true } else { - if (debug) - log.debug("cidSet device: ${device_name} no match ${match}") + logDebug("cidSet device: ${device_name} no match ${match}") } } } @@ -1986,6 +2006,57 @@ def rfxSet(evt) { def device_name = "RFX-${sn}-${bat}-${supv}-" + "${loop0}-${loop1}-${loop2}-${loop3}" + def d = getChildDevices().findAll { + it.deviceNetworkId.contains(":switch") && + it.getDataValue("serial") == sn + } + + if (d) { + d.each { + def zoneLoop = it.getDataValue("zoneLoop") + def tamperLoop = it.getDataValue("tamperLoop") + + if (zoneLoop != null && zoneLoop != "null") { + if ((zoneLoop == "1" && loop0 == "1") || (zoneLoop == "2" && loop1 == "1") || (zoneLoop == "3" && loop2 == "1") || (zoneLoop == "4" && loop3 == "1")) { + _sendEventTranslate(it, ("on"), false) + } else { + _sendEventTranslate(it, ("off"), false) + } + } + + if (tamperLoop != null && tamperLoop != "null") { + if ((tamperLoop == "1" && loop0 == "1") || (tamperLoop == "2" && loop1 == "1") || (tamperLoop == "3" && loop2 == "1") || (tamperLoop == "4" && loop3 == "1")) { + it.sendEvent( + name: "tamper", + value: "detected" + ) + } else { + it.sendEvent( + name: "tamper", + value: "clear" + ) + } + } + } + + d.each { + it.sendEvent( + name: "low_battery", + value: bat == "1" + ) + } + + if (supv == "1") { + def last_checkin = now() + d.each { + it.sendEvent( + name: "last_checkin", + value: last_checkin + ) + } + } + } + def children = getChildDevices() children.each { if (it.deviceNetworkId.contains(":RFX-")) { @@ -2022,11 +2093,9 @@ def rfxSet(evt) { sent = true - } else { - if (debug) log.info("rfxSet device: ${device_name} no match ${match}") - } + } } - } + } if (!sent) { log.warn("rfxSet: Could not find '${device_name}|XXX' device.") @@ -2087,12 +2156,12 @@ def addZone(evt) { * sets Contact attributes of the alarmdecoder device to open/closed */ def zoneOn(evt) { - if (debug) log.debug("zoneOn: desc=${evt.value}") + logDebug("zoneOn: desc=${evt.value}") // Find all :switch devices with a matching zone the event. def d = getChildDevices().findAll { it.deviceNetworkId.contains(":switch") && - it.getDataValue("zone") == evt.value + it.getDataValue("zone") == evt.value && it.getDataValue("serial") == null } if (d) { @@ -2110,11 +2179,11 @@ def zoneOn(evt) { * sets Contact attributes of the alarmdecoder device to open/closed */ def zoneOff(evt) { - if (debug) log.debug("zoneOff: desc=${evt.value}") + logDebug("zoneOff: desc=${evt.value}") def d = getChildDevices().findAll { it.deviceNetworkId.contains(":switch") && - it.getDataValue("zone") == evt.value + it.getDataValue("zone") == evt.value && it.getDataValue("serial") == null } if (d) { @@ -2137,9 +2206,8 @@ def monitorAlarmHandler(evt) { if (state.lastMONStatus != evt.value) { - if (debug) - log.debug("monitorAlarmHandler -- update lastMONStatus " + - "to ${evt.value} from ${state.lastMONStatus}") + logDebug("monitorAlarmHandler -- update lastMONStatus " + + "to ${evt.value} from ${state.lastMONStatus}") // Update last known MON state state.lastMONStatus = evt.value @@ -2149,8 +2217,7 @@ def monitorAlarmHandler(evt) { // Only refresh the main device that has a panel_state def device_type = device.getTypeName() if (device_type == "AlarmDecoder network appliance") { - if (debug) - log.debug("monitorAlarmHandler DEBUG-- ${device.deviceNetworkId}") + logDebug("monitorAlarmHandler DEBUG-- ${device.deviceNetworkId}") /* SmartThings */ if (isSmartThings()) { @@ -2160,7 +2227,7 @@ def monitorAlarmHandler(evt) { !device.getStateValue("panel_armed_stay")) { device.arm_away() } else { - log.trace "monitorAlarmHandler -- no send arm_away already set" + logTrace "monitorAlarmHandler -- no send arm_away already set" } } else if (evt.value == "stay" || evt.value == "armHome") { // do not send if already in that state. @@ -2168,7 +2235,7 @@ def monitorAlarmHandler(evt) { !device.getStateValue("panel_armed_stay")) { device.arm_stay() } else { - log.trace "monitorAlarmHandler -- no send arm_stay already set" + logTrace "monitorAlarmHandler -- no send arm_stay already set" } } else if (evt.value == "off" || evt.value == "disarm") { // do not send if already in that state. @@ -2176,10 +2243,10 @@ def monitorAlarmHandler(evt) { device.getStateValue("panel_armed_stay")) { device.disarm() } else { - log.trace "monitorAlarmHandler -- no send disarm already set" + logTrace "monitorAlarmHandler -- no send disarm already set" } } else - log.debug "Unknown SHM alarm value: ${evt.value}" + logDebug "Unknown SHM alarm value: ${evt.value}" } /* Hubitat */ else if (isHubitat()) { @@ -2189,7 +2256,7 @@ def monitorAlarmHandler(evt) { !device.getStateValue("panel_armed_stay")) { device.arm_away() } else { - log.trace "monitorAlarmHandler -- no send arm_away already set" + logTrace "monitorAlarmHandler -- no send arm_away already set" } } else if (evt.value == "armedHome") { // do not send if already in that state. @@ -2197,20 +2264,28 @@ def monitorAlarmHandler(evt) { !device.getStateValue("panel_armed_stay")) { device.arm_stay() } else { - log.trace "monitorAlarmHandler -- no send arm_stay already set" + logTrace "monitorAlarmHandler -- no send arm_stay already set" + } + } else if (evt.value == "armedNight") { + // do not send if already in that state. + if (!device.getStateValue("panel_armed") && + !device.getStateValue("panel_armed_night")) { + device.arm_night() + } else { + logTrace "monitorAlarmHandler -- no send arm_night already set" } - } else if (evt.value == "disarmed") { + } else if (evt.value == "disarmed") { // do not send if already in that state. if (device.getStateValue("panel_armed") || device.getStateValue("panel_armed_stay")) { device.disarm() } else { - log.trace "monitorAlarmHandler -- no send disarm already " + + logTrace "monitorAlarmHandler -- no send disarm already " + "set ${device.getStateValue('panel_armed')} " + "${device.getStateValue('panel_armed_stay')}" } } else - log.debug "Unknown HSM alarm value: ${evt.value}" + logDebug "Unknown HSM alarm value: ${evt.value}" } } } @@ -2226,21 +2301,23 @@ def alarmdecoderAlarmHandler(evt) { if (settings.monIntegration == false || settings.monChangeStatus == false) return - if (debug) - log.debug("alarmdecoderAlarmHandler -- update lastAlarmDecoderStatus " + - "to ${evt.value} from ${state.lastAlarmDecoderStatus}") + logDebug("alarmdecoderAlarmHandler -- update lastAlarmDecoderStatus " + + "to ${evt.value} from ${state.lastAlarmDecoderStatus}") state.lastAlarmDecoderStatus = evt.value if (isSmartThings()) { - /* no traslation needed already [stay,away,off] */ - if (debug) - log.debug("alarmdecoderAlarmHandler alarmSystemStatus ${evt.value}") + /* no traslation needed for [stay,away,off] but night has to be translated to stay */ + logDebug("alarmdecoderAlarmHandler alarmSystemStatus ${evt.value}") + msg = evt.value + + if (evt.value == "night") + msg = "stay" // Update last known MON state - state.lastMONStatus = evt.value + state.lastMONStatus = msg - sendLocationEvent(name: "alarmSystemStatus", value: evt.value) + sendLocationEvent(name: "alarmSystemStatus", value: msg) } else if (isHubitat()) { /* translate to HSM */ msg = "" @@ -2253,14 +2330,17 @@ def alarmdecoderAlarmHandler(evt) { msg = "armAway" nstate = "armedAway" // prevent loop } + if (evt.value == "night") { + msg = "armNight" + nstate = "armedNight" // prevent loop + } if (evt.value == "off") { msg = "disarm" nstate = "disarmed" // prevent loop } - if (debug) - log.debug("alarmdecoderAlarmHandler: hsmSetArm ${msg} " + - "last ${state.lastMONStatus} new ${nstate}") + logDebug("alarmdecoderAlarmHandler: hsmSetArm ${msg} " + + "last ${state.lastMONStatus} new ${nstate}") // Update last known MON state state.lastMONStatus = nstate @@ -2294,7 +2374,7 @@ def isHubitat() { */ def initSubscriptions() { // subscribe to the Smart Home Manager api for alarm status events - if (debug) log.debug("initSubscriptions: Subscribe to handlers") + logDebug("initSubscriptions: Subscribe to handlers") if (isSmartThings()) { subscribe(location, "alarmSystemStatus", monitorAlarmHandler) @@ -2316,7 +2396,7 @@ def initSubscriptions() { * to the local network */ def discover_alarmdecoder() { - if (debug) log.debug("discover_alarmdecoder") + logDebug("discover_alarmdecoder") def haobj = getHubAction("lan discovery ${SSDPTERM}}") @@ -2330,8 +2410,7 @@ def sendVerify(DNI, ssdpPath) { String ip = getHostAddressFromDNI(DNI) - if (debug) - log.debug("verifyAlarmDecoder: ${DNI} ssdpPath: ${ssdpPath} ip: ${ip}") + logDebug("verifyAlarmDecoder: ${DNI} ssdpPath: ${ssdpPath} ip: ${ip}") def haobj = getHubAction( @@ -2348,7 +2427,7 @@ def sendVerify(DNI, ssdpPath) { * Network Appliance. and get back the current status of the AlarmDecoder. */ def refresh_alarmdecoders() { - if (debug) log.debug("refresh_alarmdecoders") + logDebug("refresh_alarmdecoders") // just because it seems to get lost. initSubscriptions() @@ -2422,8 +2501,7 @@ def getDevices() { * Add all devices if triggered by the "Setup And Management" pages. */ def addExistingDevices() { - if (debug) - log.debug("addExistingDevices: ${input_selected_devices}") + logDebug("addExistingDevices: ${input_selected_devices}") // resubscribe just in case it was lost configureDeviceSubscriptions() @@ -2433,13 +2511,12 @@ def addExistingDevices() { if (selected_devices instanceof java.lang.String) { selected_devices = [selected_devices] } else { - log.debug("addExistingDevices: FIXME not input_selected_devices not String") + logDebug("addExistingDevices: FIXME not input_selected_devices not String") } selected_devices.each { dni-> - if (debug) - log.debug("addExistingDevices, getChildDevice(${dni})") + logDebug("addExistingDevices, getChildDevice(${dni})") def d = getChildDevice(dni) @@ -2449,8 +2526,7 @@ def addExistingDevices() { getDevices().find { k, v -> "${v.ip}:${v.port}" == dni } - if (debug) - log.debug("addExistingDevices, devices.find=${newDevice}") + logDebug("addExistingDevices, devices.find=${newDevice}") if (newDevice) { // FIXME: Save DNI details for filtering @@ -2464,9 +2540,8 @@ def addExistingDevices() { state.urn = convertHexToIP(state.ip) + ":" + convertHexToInt(state.port) - if (debug) - log.debug("AlarmDecoder webapp urn ('${state.urn}') " + - "hub ('${state.hubId}')") + logDebug("AlarmDecoder webapp urn ('${state.urn}') " + + "hub ('${state.hubId}')") try { // Create device adding the URN to its data object @@ -2505,8 +2580,8 @@ def addExistingDevices() { // Add zone contact sensors if they do not exist. // asynchronous to avoid timeout. Apps can only run for 20 seconds or // it will be killed. - for (def i = 0; i < MAX_VIRTUAL_ZONES; i++) { - if (debug) log.debug("Adding virtual zone sensor ${i}") + for (def i = 0; i < deviceCount; i++) { + logDebug("Adding virtual zone sensor ${i}") // SmartThings we do out of band with callback if (isSmartThings()) { sendEvent( @@ -2564,6 +2639,9 @@ def addExistingDevices() { // Add Arm Away switch/indicator combo if it does not exist. addAD2VirtualDevices("armAway", "Away", false, true, true) + // Add Arm Night switch/indicator combo if it does not exist. + addAD2VirtualDevices("armNight", "Night", false, true, true) + // Add Exit switch/indicator combo if it does not exist. addAD2VirtualDevices("exit", "Exit", false, true, true) @@ -2690,7 +2768,7 @@ def addAD2VirtualDevices(name, label, initstate, createButton, createContact) { * Configure subscriptions the virtual devices will send too. */ private def configureDeviceSubscriptions() { - if (debug) log.debug("configureDeviceSubscriptions") + logDebug("configureDeviceSubscriptions") def device = getChildDevice("${getDeviceKey()}") if (!device) { log.error("configureDeviceSubscriptions: Could not find primary" + @@ -2716,6 +2794,9 @@ private def configureDeviceSubscriptions() { // subscribe to arm-stay handler subscribe(device, "arm-stay-set", armStaySet, [filterEvents: false]) + + // subscribe to arm-night handler + subscribe(device, "arm-night-set", armNightSet, [filterEvents: false]) // subscribe to chime handler subscribe(device, "chime-set", chimeSet, [filterEvents: false]) @@ -2774,8 +2855,7 @@ private def configureDeviceSubscriptions() { */ private def parseEventMessage(String message) { - if (debug) - log.debug "parseEventMessage: $message" + logDebug "parseEventMessage: $message" def event = [: ] try { @@ -3039,7 +3119,7 @@ private getDeviceNamePart(d) { * Default clear = off, detected(Alerting) = on * */ -def _sendEventTranslate(ad2d, state) { +def _sendEventTranslate(ad2d, state, stateChange = true) { // Grab the devices preferences for inverting def invert = (ad2d.device.getDataValue("invert") == "true" ? true : false) @@ -3057,7 +3137,7 @@ def _sendEventTranslate(ad2d, state) { ad2d.sendEvent( name: "switch", value: (sval ? "on" : "off"), - isStateChange: true, + isStateChange: stateChange, filtered: true ) } @@ -3075,7 +3155,7 @@ def _sendEventTranslate(ad2d, state) { ad2d.sendEvent( name: "contact", value: (sval ? "open" : "closed"), - isStateChange: true, + isStateChange: stateChange, filtered: true ) } @@ -3093,7 +3173,7 @@ def _sendEventTranslate(ad2d, state) { ad2d.sendEvent( name: "motion", value: (sval ? "active" : "inactive"), - isStateChange: true, + isStateChange: stateChange, filtered: true ) } @@ -3111,7 +3191,7 @@ def _sendEventTranslate(ad2d, state) { ad2d.sendEvent( name: "shock", value: (sval ? "detected" : "clear"), - isStateChange: true, + isStateChange: stateChange, filtered: true ) } @@ -3129,7 +3209,7 @@ def _sendEventTranslate(ad2d, state) { ad2d.sendEvent( name: "carbonMonoxide", value: (sval ? "detected" : "clear"), - isStateChange: true, + isStateChange: stateChange, filtered: true ) } @@ -3147,8 +3227,20 @@ def _sendEventTranslate(ad2d, state) { ad2d.sendEvent( name: "smoke", value: (sval ? "detected" : "clear"), - isStateChange: true, + isStateChange: stateChange, filtered: true ) } } + +def logDebug(msg) { + if (settings?.debugOutput) { + log.debug msg + } +} + +def logTrace(msg) { + if (settings?.traceOutput) { + log.trace msg + } +} \ No newline at end of file