/*********************************************************************************************************************** * * A Hubitat Smart App for creating Envisalink Connection Device and Child Virtual Contact Zones * http://www.eyezon.com/ * * Copyright (C) 2018 Doug Beard * * Vista related portions and general enhancements by CybrMage * * License: * This program is free software: you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along with this program. * If not, see . * * Name: Envisalink Integration * https://github.com/omayhemo/hubitat_envisalink * ************************************************************** ********** See Release Notes at the bottom ****************** ***********************************************************************************************************************/ import groovy.json.JsonSlurper import groovy.util.XmlSlurper def version() { return "Envisalink 0.5.4" } definition( name: "Envisalink Integration", namespace: "dwb", singleInstance: true, author: "Doug Beard", description: "Integrate your DSC Alarm system, using Envisalink 3 or 4", category: "My Apps", importUrl: "https://raw.githubusercontent.com/bdwilson/hubitat_envisalink/master/hubitat_envisalink_integration_application.groovy", iconUrl: "https://dougbeardrdiag.file.core.windows.net/icons/HomeSecIcon.PNG", iconX2Url: "https://dougbeardrdiag.file.core.windows.net/icons/HomeSecIcon.PNG", iconX3Url: "https://dougbeardrdiag.file.core.windows.net/icons/HomeSecIcon.PNG", ) preferences { page(name: "mainPage") page(name: "zoneMapsPage", nextPage: "mainPage") page(name: "notificationPage", nextPage: "mainPage") page(name: "lockPage", nextPage: "mainPage") page(name: "switchPage", nextPage: "mainPage") page(name: "defineZoneMap", nextPage: "zoneMapsPage") page(name: "editZoneMapPage", nextPage: "zoneMapsPage") page(name: "codeMap", nextPage: "mainPage") page(name: "defineCodeMap", nextPage: "codeMap") page(name: "editCodeMap", nextPage: "codeMap") page(name: "aboutPage", nextPage: "mainPage") } //App Pages/Views def mainPage() { ifDebug("Showing mainPage") state.isDebug = isDebug state.creatingCodeMap = false state.creatingZone = false //state.codeMaps = [:] if(!state.envisalinkIntegrationInstalled && getChildDevices().size() == 0) { return dynamicPage(name: "mainPage", title: "", install: false, uninstall: true, nextPage: "zoneMapsPage") { showTitle() section("Define your Envisalink device") { clearStateVariables() input "PanelType", "enum", title: "Alarm Panel Type", required: true, multiple: false, options: [[0:"DSC"],[1:"Vista"]] input "envisalinkName", "text", title: "Envisalink Name", required: true, multiple: false, defaultValue: "Envisalink", submitOnChange: false input "envisalinkIP", "text", title: "Envisalink IP Address", required: true, multiple: false, defaultValue: "", submitOnChange: false input "envisalinkPassword", "text", title: "Envisalink Password", required: true, multiple: false, defaultValue: "", submitOnChange: false input "envisalinkMasterCode", "text", title: "Envisalink Master Code", required: true, multiple: false, defaultValue: "", submitOnChange: false input "envisalinkInstallerCode", "text", title: "Envisalink Installer Code", description:"Installer Code is only required for programming Exit Delays", required: false, multiple: false, defaultValue: "", submitOnChange: false } } } else { return dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) { showTitle() section("

Zone Mapping

") { href (name: "zoneMapsPage", title: "Zones", description: "Create Virtual sensors and Map them to Existing Zones in your Envisalink setup", page: "zoneMapsPage") } // section("

Code Mapping

") { // href (name: "codeMap", title: "Codes", // description: "Define friendly names for Codes by associating them to a Code Position", // page: "codeMap") // } section("

Notifications

") { href (name: "notificationPage", title: "Notifications", description: "Enable Push and TTS Messages", page: "notificationPage") } section("

Locks

") { href (name: "lockPage", title: "Locks", description: "Integrate Locks", page: "lockPage") } section("

Switches

") { href (name: "switchPage", title: "Switches", description: "Integrate Switches", page: "switchPage") } state.enableHSM = enableHSM section("

Safety Monitor

") { paragraph "Enabling Hubitat Safety Monitor Integration will tie your Envisalink state to the state of HSM. Your Envisalink will receive the Arm Away, Arm Home and Disarm commands based on the HSM state. " input "enableHSM", "bool", title: "Enable HSM Integration", required: false, multiple: false, defaultValue: false, submitOnChange: true } section("

") { href (name: "aboutPage", title: "About", description: "Find out more about Envisalink Integration", page: "aboutPage") } section("") { input "isDebug", "bool", title: "Enable Debug Logging", required: false, multiple: false, defaultValue: false, submitOnChange: true } } } } def aboutPage() { ifDebug("Showing aboutPage") dynamicPage(name: "aboutPage", title: none){ section("

Introducing Envisalink Integration

"){ paragraph "An EyezOn EnvisaLink module allows you to upgrade your existing security system with IP control ... " + "Envisalink Integration connects to your Envisalink module via Telnet, using eyezon's public TPI." paragraph "Evisalink Integration automates installation and configuration of the Envisalink Connection Driver" + " as well as Virtual Sensors representing the zones configured in your DSC or Vista Alarm system." paragraph "You must have the Hubitat Envisalink Connection driver already installed before making use of Envisalink Integration application " paragraph "Special Thanks to the Hubitat staff and cuboy29." } } } def lockPage() { ifDebug("Showing lockPage") dynamicPage(name: "lockPage", title: none){ section("

Locks

"){ paragraph "Enable Lock Integration, selected locks will lock when armed and/or unlock when disarmed" input "armLocks", "capability.lock", title: "Which locks to lock when armed?", required:false, multiple:true, submitOnChange:true input "disarmLocks", "capability.lock", title: "Which locks to unlock when disarmed?", required:false, multiple:true, submitOnChange:true input "lockCodeLock", "capability.lock", title: "Lock for Lock Codes", required: false, multiple:false, submitOnChange:true def lcText = lockCodeLock?.currentValue("lockCodes") if (!lcText?.startsWith("{")) { lcText = decrypt(lcText) } def lockCodesRaw def lockCodes = [] if (lcText) lockCodesRaw = new JsonSlurper().parseText(lcText) ifDebug("lockCodes for selected lock: ${lockCodesRaw}") lockCodesRaw.each{ //ifDebug("lockCode ${it}") def lockCodeValue = it.getValue() ifDebug("lockCodeValue.name: ${lockCodeValue.name} - lockCodeValue.code: ${lockCodeValue.code}") lockCodes << ["${lockCodeValue.code}": "${lockCodeValue.name}"] } input "selectedLockCodes", 'enum', title: "Lock Codes that disarm", required: false, multiple:true, submitOnChange:true, options: lockCodes ifDebug("lock codes options: ${lockCodes}") ifDebug("selected lock codes: ${selectedLockCodes}") } } } def switchPage() { ifDebug("Showing switchPage") dynamicPage(name: "switchPage", title: none){ section("

Switches

"){ paragraph "Integrating Switches" input "onSwitches", "capability.switch", title: "Which switches to turn On when Armed and Off when Disarmed?", required:false, multiple:true, submitOnChange:true input "onSwitchDelayArmed", "number", title: "Delay On by how many minutes?", required: true, multiple: false, defaultValue: 0, range: "0..120", submitOnChange: true input "onSwitchDelayDisarmed", "number", title: "Delay Off by how many minutes?", required: true, multiple: false, defaultValue: 0, range: "0..120", submitOnChange: true input "offSwitches", "capability.switch", title: "Which switches to turn Off when Armed and On when Disarmed?", required:false, multiple:true, submitOnChange:true input "offSwitchDelayArmed", "number", title: "Delay On by how many minutes?", required: true, multiple: false, defaultValue: 0, range: "0..120", submitOnChange: true input "offSwitchDelayDisarmed", "number", title: "Delay Off by how many minutes?", required: true, multiple: false, defaultValue: 0, range: "0..120", submitOnChange: true input "offNoOnSwitches", "capability.switch", title: "Which switches to turn Off when Armed only?", required:false, multiple:true, submitOnChange:true input "offNoOnSwitchDelayArmed", "number", title: "Delay Off by how many minutes?", required: true, multiple: false, defaultValue: 0, range: "0..120", submitOnChange: true ifDebug("Switches on when Armed: ${onSwitches}") ifDebug("Switches off when Armed: ${offSwitches}") } } } def notificationPage(){ dynamicPage(name: "notificationPage", title: none){ section("

Notifications

"){ paragraph "Enable TTS and Notification integration will announcing arming and disarming over your supported audio and/or push enabled device" paragraph "

Notification Text

" input "armingHomeBool", "bool", title: "Enable Arming Home Notification", required: false, multiple: false, defaultValue: false, submitOnChange: true if (armingHomeBool){ input "armingHomeText", "text", title: "Notification for Arming Home", required: false, multiple: false, defaultValue: "Arming Home", submitOnChange: false, visible: armingHomeBool } input "armingAwayBool", "bool", title: "Enable Arming Away Notification", required: false, multiple: false, defaultValue: false, submitOnChange: true if (armingAwayBool){ input "armingAwayText", "text", title: "Notification for Arming Away", required: false, multiple: false, defaultValue: "Arming Away", submitOnChange: false } input "armingNightBool", "bool", title: "Enable Arming Night Notification", required: false, multiple: false, defaultValue: false, submitOnChange: true if (armingNightBool){ input "armingNightText", "text", title: "Notification for Arming Night", required: false, multiple: false, defaultValue: "Arming Night", submitOnChange: false } input "armedBool", "bool", title: "Enable Armed Notification", required: false, multiple: false, defaultValue: false, submitOnChange: true if (armedBool){ input "armedText", "text", title: "Notification for Armed", required: false, multiple: false, defaultValue: "Armed", submitOnChange: false } input "disarmingBool", "bool", title: "Enable Disarming Notification", required: false, multiple: false, defaultValue: false, submitOnChange: true if (disarmingBool){ input "disarmingText", "text", title: "Notification for Disarming", required: false, multiple: false, defaultValue: "Disarming", submitOnChange: false } input "disarmedBool", "bool", title: "Enable Disarmed Notification", required: false, multiple: false, defaultValue: false, submitOnChange: true if (disarmedBool){ input "disarmedText", "text", title: "Notification for Disarmed", required: false, multiple: false, defaultValue: "Disarmed", submitOnChange: false } input "entryDelayAlarmBool", "bool", title: "Enable Entry Delay Notification", required: false, multiple: false, defaultValue: false, submitOnChange: true if (entryDelayAlarmBool){ input "entryDelayAlarmText", "text", title: "Notification for Entry Delay", required: false, multiple: false, defaultValue: "Entry Delay in Progress, Alarm eminent", submitOnChange: false } input "exitDelayAlarmBool", "bool", title: "Enable Exit Delay Notification", required: false, multiple: false, defaultValue: false, submitOnChange: true if (exitDelayAlarmBool){ input "exitDelayAlarmText", "text", title: "Notification for Exit Delay", required: false, multiple: false, defaultValue: "", submitOnChange: false } input "alarmBool", "bool", title: "Enable Alarm Notification", required: false, multiple: false, defaultValue: false, submitOnChange: true if (alarmBool){ input "alarmText", "text", title: "Notification for Alarm", required: false, multiple: false, defaultValue: "Alarm, Alarm, Alarm, Alarm, Alarm", submitOnChange: false } paragraph "

Notification Devices

" input "speechDevices", "capability.speechSynthesis", title: "Which speech devices?", required:false, multiple:true, submitOnChange:true if(speechDevices){input "defaultVol", "number", title: "Fixed speaker Volume", description: "0-100%", defaultValue: "70", required: true} input "notificationDevices", "capability.notification", title: "Which notification devices?", required:false, multiple:true, submitOnChange:true } } } def codeMap() { ifDebug("Showing codeMap") if (state.creatingCodeMap) { createCodeMap() } dynamicPage(name: "codeMap", title: "", install: false, uninstall: false){ section("

Code Maps

"){ paragraph "The partition of your DSC supports up to 42 codes for arming and disarming. The TPI will send the code position to Envisalink Integration app." + "You'll need to determine which user is in which position in the DSC setup, prior to mapping those users to friendly names here " } section("") { href (name: "createUserMapPage", title: "Create a Code Map", description: "Define a friendly name for each known Code Map", page: "defineCodeMap") } section("

Existing Codes

"){ state.codeMaps.each{ href (name: "editCodeMap", title: "${it}", description: "Code Details", params: [codeMapNumber: it.key], page: "editCodeMap") } } } } def defineCodeMap() { ifDebug("Showing defineCodeMap") if (state.codeMaps[codeNumber] != null){ logError("Code Map Already Exists") } if (codeNumber && state.codeMaps[codeNumber as int] == null){ state.creatingCodeMap = true; } dynamicPage(name: "defineCodeMap", title: ""){ section("

Create a Code Map

"){ paragraph "Create a Map for a Code in Envisalink" input "codeName", "text", title: "Code Name", required: true, multiple: false, submitOnChange: false input "codeNumber", "number", title: "Which Position 1-64", required: true, multiple: false, range: "1..42", submitOnChange: true } } } def editCodeMap(message) { ifDebug("Showing editCodeMap") dynamicPage(name: "editCodeMap", title: ""){ section("

Edit a Code Map

"){ paragraph "Coming Soon" //input "codeName", "text", title: "Code Name", required: true, multiple: false, submitOnChange: false //input "codeNumber", "number", title: "Which Position 1-64", required: true, multiple: false, range: "1..42", submitOnChange: true } } //ifDebug("editing ${message.deviceNetworkId}") //state.allZones = getEnvisalinkDevice().getChildDevices() //def zoneDevice = getEnvisalinkDevice().getChildDevice(message.deviceNetworkId) //def paragraphText = "" //state.editedZoneDNI = message.deviceNetworkId; // if (zoneDevice.capabilities.find { item -> item.name.startsWith('Motion')}){ // paragraphText = paragraphText + "Motion Sensor\n" // } // if (zoneDevice.capabilities.find { item -> item.name.startsWith('Contact')}){ // paragraphText = paragraphText + "Contact Sensor\n" // } // dynamicPage(name: "editZoneMapPage", title: ""){ // section("

${zoneDevice.label}

"){ // paragraph paragraphText // } // } } def zoneMapsPage() { ifDebug("Showing zoneMapsPage") def childDevices = getChildDevices() ifDebug("getChildDevice: ${childDevices}") if (getChildDevices().size() == 0 && !state.envisalinkIntegrationInstalled) { createEnvisalinkParentDevice() } if (state.creatingZone) { createZone() } if (state.editingZone) { editZone() } dynamicPage(name: "zoneMapsPage", title: "", install: false, uninstall: false){ section("

Zone Maps

"){ paragraph "The partition of your Envisalink Installation should be divided into Zones. " + "You can map each Zone to a Virtual sensor component device in Envisalink Integration " paragraph "You'll want to determine the Zone number as it is defined in " + "your Envisalink setup. Define a new Zone in Envisalink Integration and the " + "application will then create a Virtual sensor component device, which will report " + "the state of the Envisalink Zone to which it is mapped. The Virtual sensor components " + "can be used in Rule Machine or any other application that is capable of leveraging the " + "Contact/Motion/CO/Smoke/Shock capabilities." paragraph "Envisalink is capable of 48 zones (Vista) or 64 zones (DSC), your zone map should correspond to the numeric representation of those zones." } section("Create a Zone Map") { href (name: "createZoneMapPage", title: "Create a Zone Map", description: "Create a Virtual Contact Zone", page: "defineZoneMap") } section("

Existing Zones

"){ getEnvisalinkDevice().getChildDevices().each{ href (name: "editZoneMapPage", title: "${it.name} - ${it.label}", description: "Zone Details\t${it.getTypeName()}", params: [deviceNetworkId: it.deviceNetworkId], page: "editZoneMapPage") } } } } def defineZoneMap() { ifDebug("Showing defineZoneMap") state.creatingZone = true; app.clearSetting("zoneName") app.clearSetting("zoneNumber") app.clearSetting("zoneType") dynamicPage(name: "defineZoneMap", title: ""){ section("

Create a Zone Map

"){ paragraph "Create a Map for a zone in Envisalink" input "zoneName", "text", title: "Zone Name", required: true, multiple: false, defaultValue: "Zone x", submitOnChange: false input "zoneNumber", "number", title: "Which Zone 1-64", required: true, multiple: false, defaultValue: 001, range: "1..64", submitOnChange: false input "zoneType", "enum", title: "Type of Sensor?", required: true, multiple: false, options: [[0:"Contact"],[1:"Motion"],[2:"CO"],[3:"Smoke"],[4:"GlassBreak"]] } } } def editZoneMapPage(message) { ifDebug("Showing editZoneMapPage") ifDebug("editing ${message.deviceNetworkId}") state.allZones = getEnvisalinkDevice().getChildDevices() def zoneDevice = getEnvisalinkDevice().getChildDevice(message.deviceNetworkId) def paragraphText = "" def zType = "Contact" state.editingZone = true state.editedZoneDNI = message.deviceNetworkId; log.debug("editing zoneDevice: name = '${zoneDevice.name}' label = '${zoneDevice.label}' ") if (zoneDevice.capabilities.find { item -> item.name.startsWith('Motion')}){ paragraphText = paragraphText + "Motion Sensor\n" zType = "Motion" } if (zoneDevice.capabilities.find { item -> item.name.startsWith('Contact')}){ paragraphText = paragraphText + "Contact Sensor\n" zType = "Contact" } if (zoneDevice.capabilities.find { item -> item.name.startsWith('CarbonMonoxide')}){ paragraphText = paragraphText + "CO Sensor\n" zType = "CO" } if (zoneDevice.capabilities.find { item -> item.name.startsWith('Smoke')}){ paragraphText = paragraphText + "Smoke Sensor\n" zType = "Smoke" } if (zoneDevice.capabilities.find { item -> item.name.startsWith('Shock')}){ paragraphText = paragraphText + "GlassBreak Sensor\n" zType = "GlassBreak" } app.clearSetting("newZoneLabel") app.clearSetting("newZoneType") dynamicPage(name: "editZoneMapPage", title: ""){ section("

Edit a zone in Envisalink

"){ paragraph "${zoneDevice.name}" paragraph paragraphText paragraph "" input "newZoneLabel", "text", title: "Zone Label", required: true, multiple: false, defaultValue: zoneDevice.label, submitOnChange: false input "newZoneType", "enum", title: "Type of Sensor?", required: true, multiple: false, defaultValue: zType, options: [[0:"Contact"],[1:"Motion"],[2:"CO"],[3:"Smoke"],[4:"GlassBreak"],[99:"REMOVE ZONE"]] } } } def clearStateVariables(){ ifDebug("Clearing State Variables just in case.") state.EnvisalinkDeviceName = null state.EnvisalinkIP = null state.EnvisalinkPassword = null state.EnvisalinkCode = null } def createEnvisalinkParentDevice(){ ifDebug("Creating Parent Envisalink Device") if (getEnvisalinkDevice() == null){ state.EnvisalinkDNI = UUID.randomUUID().toString() ifDebug("Setting state.EnvisalinkDNI ${state.EnvisalinkDNI}") addChildDevice("dwb", "Envisalink Connection", state.EnvisalinkDNI, null, [name: envisalinkName, isComponent: true, label: envisalinkName]) getEnvisalinkDevice().updateSetting("PanelType",[type:"enum", value:PanelType]) getEnvisalinkDevice().updateSetting("ip",[type:"text", value:envisalinkIP]) getEnvisalinkDevice().updateSetting("passwd",[type:"text", value:envisalinkPassword]) getEnvisalinkDevice().updateSetting("masterCode",[type:"text", value:envisalinkMasterCode]) getEnvisalinkDevice().updateSetting("installerCode",[type:"text", value:envisalinkInstallerCode]) castEnvisalinkDeviceStates() } } def castEnvisalinkDeviceStates(){ ifDebug("Casting to State Variables") state.PanelType = PanelType ifDebug("Setting state.PanelType ${state.PanelType}") state.EnvisalinkDeviceName = envisalinkName ifDebug("Setting state.EnvisalinkDeviceName ${state.EnvisalinkDeviceName}") state.EnvisalinkIP = envisalinkIP ifDebug("Setting state.EnvisalinkIP ${state.EnvisalinkIP}") state.EnvisalinkPassword = envisalinkPassword ifDebug("Setting state.EnvisalinkPassword ${state.EnvisalinkPassword}") state.EnvisalinkCode = envisalinkMasterCode ifDebug("Setting state.EnvisalinkCode ${state.EnvisalinkCode}") state.EnvisalinkInstallerCode = envisalinkInstallerCode ifDebug("Setting state.EnvisalinkInstallerCode ${state.envisalinkInstallerCode}") if (getEnvisalinkDevice()){ ifDebug("Found a Child Envisalink ${getEnvisalinkDevice().label}") } else{ ifDebug("Did not find a Parent Envisalink") } } def createCodeMap(){ ifDebug("createCodeMap") def newMap = [(codeNumber as int):(codeName)] ifDebug("New Map: ${newMap}") if (state.codeMaps == null){ state.codeMaps = [] } state.codeMaps << [(codeNumber as int):(codeName)] } def createZone(){ ifDebug("Starting validation of ${zoneName} ZoneType: ${zoneType}") createNewZone(zoneName, zoneType, zoneNumber) } def createNewZone(zoneName, zoneType, zoneNumber) { String formatted = String.format("%03d", zoneNumber) String deviceNetworkId if (zoneType == "0"){ deviceNetworkId = state.EnvisalinkDNI + "_" + formatted } else if (zoneType == "1"){ deviceNetworkId = state.EnvisalinkDNI + "_M_" + formatted } else if (zoneType == "2"){ deviceNetworkId = state.EnvisalinkDNI + "_C_" + formatted } else if (zoneType == "3"){ deviceNetworkId = state.EnvisalinkDNI + "_S_" + formatted } else if (zoneType == "4"){ deviceNetworkId = state.EnvisalinkDNI + "_G_" + formatted } ifDebug("Entered zoneNumber: ${zoneNumber} formatted as: ${formatted}") getEnvisalinkDevice().createZone([zoneName: "Zone " + formatted, zoneLabel: zoneName, deviceNetworkId: deviceNetworkId, zoneType: zoneType]) state.creatingZone = false; } def editZone(){ def childZone = getEnvisalinkDevice().getChildDevice(state.editedZoneDNI); // log.debug("(editZone) childZone [${childZone}]") // log.debug("Starting validation of device [${state.editedZoneDNI}] (" + childZone.getDisplayName()) + ")" // log.debug("Attempting rename of zone to [${newZoneLabel}]") if (childZone) { def childZoneId = childZone.getDeviceNetworkId().reverse().take(3).reverse() def childZoneLabel = childZone.getDisplayName() def CZT = childZone.getDeviceNetworkId().tokenize("_") def childZoneType = (CZT.size() < 3)? "0":(CZT[1] == "M")? "1":(CZT[1] == "C")? "2":(CZT[1] == "S")? "3":(CZT[1] == "G")? "4": "0" log.debug("CZT size = ${CZT.size()} CZT [${CZT}] childZoneType [${childZoneType}]") log.debug("Attempting edit of zone: [${childZoneLabel}] [${childZoneType}] [${childZoneId}] to [${newZoneLabel}] [${newZoneType}] [${childZoneId}]") if (childZoneType != newZoneType) { // remove the zone and recreate getEnvisalinkDevice().removeZone(zoneName:childZone.getDisplayName(), deviceNetworkId: childZone.getDeviceNetworkId()) log.debug("Removed zone: [${childZoneLabel}] [${childZoneType}] [${childZoneId}]") if (newZoneType != "99") { //recreate the zone createNewZone(newZoneLabel, newZoneType, childZoneId.toInteger()) log.debug("Created zone: [${childZoneLabel}] [${childZoneType}] [${childZoneId}]") } } else if (childZoneLabel != newZoneLabel) { childZone.setDisplayName(newZoneLabel) } } newZoneType = null newZoneName = null; state.editingZone = false state.editedZoneDNI = null; } //events and actions def hsmHandler(evt) { //def myStatus = getEnvisalinkDevice().currentValue("Status") //log.info "ENVIS HSM Value: $evt.value Status: ${myStatus} LAST: $state.lastHSMEvent" // //the call below appears unnecessary and causes it to throw a quick disarm hsm event during armHome/armAway events. //sendEvent(name: "HSM Event", value: evt.value) if (evt.value == state.lastHSMEvent) return state.lastHSMEvent = evt.value def lock if (!lock) { lock = true if (getEnvisalinkDevice().currentValue("Status") != "Exit Delay in Progress" && getEnvisalinkDevice().currentValue("Status") != "Entry Delay in Progress" && evt.value != "disarmed") { if (evt.value && state.enableHSM) { ifDebug("HSM is enabled") ifDebug("Current Status: ${getEnvisalinkDevice().currentValue("Status").contains("Armed")}") if (!getEnvisalinkDevice().currentValue("Status").contains("Armed")) { switch(evt.value){ case "armedAway": ifDebug("Sending Arm Away") speakArmingAway() getEnvisalinkDevice().ArmAway() //myStatus = getEnvisalinkDevice().currentValue("Status") //log.info "HSM Sending evt.value=armedAway: getEnvisalinkDevice status=${myStatus}" break case "armedHome": ifDebug("Sending Arm Home") speakArmingHome() getEnvisalinkDevice().ArmHome() //myStatus = getEnvisalinkDevice().currentValue("Status") //log.info "HSM Sending evt.value=armedHome: getEnvisalinkDevice status=${myStatus}" break case "armedNight": ifDebug("Sending Arm Night") speakArmingNight() getEnvisalinkDevice().ArmNight() //myStatus = getEnvisalinkDevice().currentValue("Status") //log.info "ENVIS HSM Arming Night: Status: ${myStatus}" break } } } } else { if (evt.value == "disarmed") { if (state.enableHSM) { ifDebug("HSM is enabled") ifDebug("Sending Disarm") if (getEnvisalinkDevice().currentValue("Status") != "Ready" && getEnvisalinkDevice().currentValue("Status") != "Disarmed") { speakDisarming() getEnvisalinkDevice().Disarm() //myStatus = getEnvisalinkDevice().currentValue("Status") //log.info "HSM Sending evt.value=disarmed HSM getEnvisalinkDevice status: ${myStatus}" } } } } lock = false; } } def speakArmed(){ if (!armedBool) return if (armedText != ""){ speakIt(armedText) } } def speakArmingAway(){ if (!armingAwayBool) return if (armingAwayText){ speakIt(armingAwayText) } else { speakIt("Arming Away") } } def speakArmingHome(){ if (!armingHomeBool) return if (armingHomeText != ""){ speakIt(armingHomeText) } } def speakArmingNight(){ if (!armingNightBool) return if (armingNightText != ""){ speakIt(armingNightText) } } def speakDisarming(){ if (!disarmingBool) return if (disarmedText){ speakIt(disarmingText) } else { speakIt("Disarming") } } def speakDisarmed(){ if (!disarmedBool) return if (disarmedText != ""){ speakIt(disarmedText) } } def speakEntryDelay(){ if (!entryDelayAlarmBool) return if (entryDelayAlarmText != ""){ speakIt(entryDelayAlarmText) } } def speakExitDelay(){ if (!exitDelayAlarmBool) return if (exitDelayAlarmText != ""){ speakIt(exitDelayAlarmText) } } def speakAlarm(){ if (!alarmBool) return if (alarmText != ""){ speakIt(alarmText) } } private speakIt(str) { if (state.lastPhaseSpoken == str) return state.lastPhaseSpoken = str; ifDebug("TTS: $str") if (state.speaking) { ifDebug("Already Speaking") runOnce(new Date(now() + 10000), speakRetry, [overwrite: false, data: [str: str]]) return } if (speechDevices) { ifDebug("Found Speech Devices") state.speaking = true speechDevices.each { def prevVolume = it.currentValue("volume") if (it.hasCommand('setVolumeSpeakAndRestore')){ it.setVolumeSpeakAndRestore(defaultVol, str, prevVolume) } else { it.speak(str) } } } if (notificationDevices){ ifDebug("Found Notification Devices") notificationDevices.deviceNotification(str) } state.speaking = false } private speakRetry(data) { if (data.str) speakIt(data.str); } private lockIt(){ ifDebug("Lock") if (!armLocks) return ifDebug("Found Lock") armLocks.lock() } private unlockIt(){ ifDebug("Unlock") if (!disarmLocks) return ifDebug("Found Lock") disarmLocks.unlock() } def switchItArmed(){ ifDebug("switchItArmed") ifDebug("On Delay: ${onSwitchDelayArmed}") ifDebug("Off Delay: ${offSwitchDelayArmed}") if (onSwitchDelayArmed){ runIn(onSwitchDelayArmed*60, onSwitchesOn) } else { onSwitchesOn() } if (offSwitchDelayArmed){ runIn(offSwitchDelayArmed*60, offSwitchesOff) } else { offSwitchesOff() } if (offNoOnSwitchDelayArmed){ runIn(offNoOnSwitchDelayArmed*60, offNoOnSwitchesOff) } else { offNoOnSwitchesOff() } } def switchItDisarmed(){ ifDebug("switchItDisarmed") ifDebug("On Delay: ${offSwitchDelayDisarmed}") ifDebug("Off Delay: ${onSwitchDelayDisarmed}") if (onSwitchDelayDisarmed){ runIn(onSwitchDelayDisarmed*60, onSwitchesOff) } else { onSwitchesOff() } if (offSwitchDelayDisarmed){ runIn(offSwitchDelayDisarmed*60, offSwitchesOn) } else { offSwitchesOn() } } def onSwitchesOn(){ if (!onSwitches) return ifDebug("Armed Switches On") onSwitches.on() } def onSwitchesOff(){ if (!onSwitches) return ifDebug("Armed Switches Off") onSwitches.off() } def offSwitchesOn(){ if (!offSwitches) return ifDebug("Disarmed Switches On") offSwitches.on() } def offSwitchesOff(){ if (!offSwitches) return ifDebug("Disarmed Switches Off") offSwitches.off() } def offNoOnSwitchesOff(){ if (!offNoOnSwitches) return ifDebug("Disarmed Switches Off [Only]") offNoOnSwitches.off() } def lockUseHandler(evt){ log.warn "lockUseHandler ${evt.displayName}" def data = evt.data def isEncrypted = false if (data && !data[0].startsWith("{")) { ifDebug("encr data:${data}") data = decrypt(data) isEncrypted = true } if (data){ ifDebug("lockUseHandler- device:${evt.displayName}, value:${evt.value}, data:${data}, type:${evt.type}, wasEncrypted:${isEncrypted}") def dataJson = new JsonSlurper().parseText(data) ifDebug("dataJson - ${dataJson}") dataJson.each{ ifDebug(it) def lockCodeValue = it.getValue() ifDebug(lockCodeValue) ifDebug(lockCodeValue.code) def foundCode = selectedLockCodes.find{it == lockCodeValue.code} if (foundCode){ ifDebug("Found Lock Code") getEnvisalinkDevice().Disarm() } } } } // def userCodeDisarm(data){ // def code = data as int // ifDebug("userCodeDisarm: ${code}") // if(state.codeMaps[code.toString()]){ // ifDebug("Disarm Code Used: ${state.codeMaps[code.toString()].value}") // //TODO: Do something with this knowledge // } // } // def userCodeArm(data){ // def code = data as int // ifDebug("userCodeArm: ${code}") // ifDebug(state.codeMaps) // def found = state.codeMaps[code] // ifDebug(found) // if(found){ // ifDebug("Arm Code Used: ${found.value}") // } // } private removeChildDevices(delete) { delete.each {deleteChildDevice(it.deviceNetworkId)} } def showTitle(){ def appVersion = version() def driverVersion = (getEnvisalinkDevice() == null) ? "Not Yet Installed" : getEnvisalinkDevice().version() state.version = " App [" + appVersion + "] Driver [" + driverVersion + "]" section(){paragraph "
Version: $state.version
"} } private ifDebug(msg){ if (msg && state.isDebug) log.debug 'Envisalink Integration: ' + msg } private logError(msg){ if (msg) log.error 'Envisalink Integration: ' + msg } def getEnvisalinkDevice(){ ifDebug("getEnvisalinkDevice") def childDevices = getChildDevices() // ifDebug("childDevices: ${childDevices}") def envisalinkDevice = childDevices[0] // ifDebug("childDevices: ${envisalinkDevice}") return envisalinkDevice } //General App Events def installed() { state.envisalinkIntegrationInstalled = true initialize() } def updated() { log.info "updated" initialize() } def initialize() { log.info "initialize" sendEvent(name: "Initialized", value: "online") unsubscribe() state.creatingCodeMap = false; state.creatingZone = false; if (state.codeMaps == null){ state.codeMaps = [:] } log.info "subscribing" subscribe(location, "hsmStatus", hsmHandler) subscribe(lockCodeLock,"lock",lockUseHandler) } def uninstalled() { removeChildDevices(getChildDevices()) } /*********************************************************************************************************************** * Version: 0.5.3 * Fixed spurious HSM Event in hsmHandler() * * Version: 0.5.2 * Addtional Vista fixes and improvements from Cybrmage * New device types supported - CO2, Smoke, Glassbreak (requires external driver) * * Version: 0.5.1 * Fix child device variable mix up * * Version: 0.5 * Fix duplicate Disarm command caused by HSM * * Version: 0.4.1 * Using hasCommand, re-implement setVolumeSpeakAndRestore * * Version: 0.4 * Little commented out code maybe for setting up custom actions based on codes. * Honestly I am considering managing all that in the driver, like a lock does. * * Version: 0.3.6 * Fixed speakIt routine * * Version: 0.3.5 * Fixed error handling on delays when null * * Version: 0.3.4 * Home and Away Armed states * * Version: 0.3.3 * Fix installation process * * Version: 0.3.2 * Add switch off on disarm with no on with arm * * Version: 0.3.1 * Fix dumb mistake in runin delay * * Version: 0.3.0 * UI Changes * Minor Fixes * Lock Code Integration * Switch Integration * Debouncing * Improved Error Logging * * Version: 0.2.0 * UI Changes * * Version: 0.17.0 * Added TTS * Locks * Notifications * * Version: 0.16.0 * Fixed HSM Integration Initialization to default to False * Fixed Debug Toggle * * Version: 0.15.0 * Add deeper integration with HSM * * Version: 0.14.0 * Added armedNight Handler * * Version: 0.13.0 * Provided Debug switch for less logging if desired. * Moved this list to bottom of file * * Version: 0.12.1 * HSM Integration Changes * * Version: 0.11.0 * Added Motion Zone Capability * * Version: 0.10.0 * * Just the basics. * Creates the Envisalink Connection device and allows definition of Zone Maps, creating virtual contact sensors as child components. * Allows subscription to HSM to mirror the state of HSM to Envisalink (ArmAway, ArmHome, Disarm) */