Guest User

AlarmDecover service

a guest
Aug 28th, 2018
94
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Groovy 50.11 KB | None | 0 0
  1. /**
  2.  *  AlarmDecoder Service Manager
  3.  *
  4.  *  Copyright 2016-2018 Nu Tech Software Solutions, Inc.
  5.  *
  6.  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  7.  *  in compliance with the License. You may obtain a copy of the License at:
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  12.  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  13.  *  for the specific language governing permissions and limitations under the License.
  14.  *
  15.  *
  16.  * Version 1.0.0 - Scott Petersen - Initial design and release
  17.  * Version 2.0.0 - Sean Mathews <coder@f34r.com> - Changed to use UPNP Push API in AD2 web app
  18.  * Version 2.0.1 - Sean Mathews <coder@f34r.com> - Adding CID device management support.
  19.  */
  20.  
  21. /*
  22.  * global support
  23.  */
  24. import groovy.transform.Field
  25.  
  26. /*
  27.  * Turn on verbose debugging
  28.  */
  29. @Field debug = false
  30.  
  31. /*
  32.  * Device label name settings
  33.  */
  34. @Field lname = "AlarmDecoder"
  35. @Field sname = "AD2"
  36.  
  37. definition(
  38.     name: "AlarmDecoder service",
  39.     namespace: "alarmdecoder",
  40.     author: "Nu Tech Software Solutions, Inc.",
  41.     description: "AlarmDecoder (Service Manager)",
  42.     category: "My Apps",
  43.     iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
  44.     iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
  45.     iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
  46.     singleInstance: true) { }
  47.  
  48. preferences {
  49.     page(name: "pageMain", title: titles("page_main"), content: "page_main", install: false, uninstall: false, refreshInterval: 5)
  50.     page(name: "pageDiscoverDevices", title: titles("page_discover_devices"), content: "page_discover_devices", install: true, uninstall: false)
  51.     page(name: "pageRemoveAll", title: titles("page_remove_all"), content: "page_remove_all", install: false, uninstall: false)
  52.     page(name: "pageCidManagement", title: titles("page_cid_management"), content: "page_cid_management", install: false, uninstall: false)
  53.     page(name: "pageAddNewCid", title: titles("page_add_new_cid"), content: "page_add_new_cid", install: false, uninstall: false)
  54.     page(name: "pageAddNewCidConfirm", title: titles("page_add_new_cid_confirm", buildcidlabel()), content: "page_add_new_cid_confirm", install: false, uninstall: false)
  55.     page(name: "pageRemoveSelectedCid", title: titles("page_remove_selected_cid"), content: "page_remove_selected_cid", install: false, uninstall: false)
  56. }
  57.  
  58. /*
  59.  * Localization strings
  60.  */
  61.  
  62. // string table for titles
  63. def titles(String name, Object... args) {
  64.   def page_titles = [
  65.     "page_main": "${lname} setup and management",
  66.     "page_discover_devices": "Install ${lname} service",
  67.     "page_remove_all": "Remove all ${lname} devices",
  68.     "confirm_remove_all": "Confirm remove all",
  69.     "page_cid_management": "Contact ID device management",
  70.     "page_add_new_cid": "Add new CID virtual switch",
  71.     "page_add_new_cid_confirm": "Add new CID switch : %s",
  72.     "page_remove_selected_cid": "Remove selected virtual switches",
  73.     "href_refresh_devices": "Send UPNP discovery",
  74.     "section_cid_value": "Build CID Value(USER/ZONE) : %s",
  75.     "section_cid_partition": "Select the CID partition",
  76.     "section_cid_names": "Device Name and Label",
  77.     "section_build_cid": "Build CID Number :",
  78.     "input_cid_name": "Enter the new device name or blank for auto",
  79.     "input_cid_label": "Enter the new device label or blank for auto",    
  80.     "info_page_remove_selected_cid": "Attempted to remove selected devices. This may fail if the device is in use. If it fails review the log and manually remove the usage. Press back to continue.",
  81.     "info_add_new_cid_confirm": "Attempted to add new CID device. This may fail if the device is in use. If it fails review the log. Press back to continue.",
  82.     "info_remove_all_a": "Removed all child devices.",
  83.     "info_remove_all_b": "This will attempt to remove all child devices. This may fail if the device is in use. If it fails review the log and manually remove the usage. Press back to continue.",
  84.     "input_cid_devices": "Remove installed CID virutal switches",
  85.     "input_cid_number": "Select the CID number for this device",
  86.     "input_cid_value": "Zero PAD 3 digit User,Zone or simple regex pattern ex. '001' or '...'",
  87.     "input_cid_partition": "Enter the partition or 0 for system",
  88.     "input_cid_number_raw": "Enter CID # or simple regex pattern",
  89.     "input_selected_devices": "Select device(s) (%s found)",
  90.     "defaultSensorToClosed": "Default zone sensors to closed?",
  91.     "shmIntegration": "Integrate with Smart Home Monitor?",
  92.     "shmChangeSHMStatus": "Automatically change Smart Home Monitor status when armed or disarmed?",
  93.   ]
  94.   if (args)
  95.       return String.format(page_titles[name], args)
  96.   else
  97.       return page_titles[name]
  98. }
  99.  
  100. // string table for descriptions
  101. def descriptions(name, Object... args) {
  102.   def element_descriptions = [
  103.     "href_discover_devices": "Tap to discover and install your ${lname} Appliance",
  104.     "href_remove_all": "Tap to remove all ${lname} virtual devices",
  105.     "href_cid_management": "Tap to manage CID virtual switches",
  106.     "input_cid_devices": "Tap to select",
  107.     "input_cid_number": "Tap to select",
  108.     "href_refresh_devices": "Tap to select",
  109.     "href_remove_selected_cid": "Tap to remove selected virtual switches",
  110.     "href_add_new_cid": "Tap to add new CID switch",
  111.     "href_add_new_cid_confirm": "Tap to confirm and add"
  112.   ]
  113.   if (args)
  114.       return String.format(element_descriptions[name],args)
  115.   else
  116.       return element_descriptions[name]
  117. }
  118.  
  119. /**
  120.  * Allow remote device to force the HUB to request an
  121.  * update from the AlarmDecoder.
  122.  *
  123.  * Just a few steps :( but it works.
  124.  * AD2 -> ST-CLOUD -> ST-HUB -> AD2 -> ST-HUB -> ST-CLOUD
  125.  */
  126. mappings {
  127.     path("/update") {
  128.         action: [
  129.             GET: "webserviceUpdate"
  130.         ]
  131.     }
  132. }
  133.  
  134. /**
  135.  * Section note on saving and < icons
  136.  */
  137. def section_no_save_note() {section("Please note") { paragraph "Do not use the \"<\" or the \"Save\" buttons on this page."}}
  138.  
  139. /**
  140.  * our main page dynamicly generated to control page based upon logic.
  141.  */
  142. def page_main() {
  143.  
  144.     // make sure we are listening to all network subscriptions
  145.     initSubscriptions()
  146.  
  147.     // send out a UPNP broadcast discovery
  148.     discover_alarmdecoder()
  149.  
  150.     // see if we are already installed
  151.     def foundMsg = ""
  152.     def mainDevice = getChildDevice("${state.ip}:${state.port}")
  153.     if (mainDevice) foundMsg = "**** AlarmDecoder already installed ****"
  154.  
  155.     dynamicPage(name: "page_main") {
  156.         if (!mainDevice) {
  157.             section("") {
  158.                 href("pageDiscoverDevices", required: false, title: titles("page_discover_devices"), description: descriptions("href_discover_devices"))
  159.             }
  160.         } else {
  161.             section("") {
  162.                 paragraph(foundMsg)
  163.             }
  164.             section("") {
  165.                 href("pageRemoveAll", required: false, title: titles("page_remove_all"), description: descriptions("href_remove_all"))
  166.             }        
  167.             section("") {
  168.                 href("pageCidManagement", required: false, title: titles("page_cid_management"), description: descriptions("href_cid_management"))
  169.             }
  170.         }
  171.     }
  172. }
  173.  
  174. /**
  175.  * Page page_cid_management generator.
  176.  */
  177. def page_cid_management() {
  178.     // TODO: Find a way to clear our current values on loading page
  179.     return dynamicPage(name: "page_cid_management") {
  180.         def found_devices = []
  181.         getAllChildDevices().each { device ->
  182.             if (device.deviceNetworkId.contains(":CID-"))
  183.             {
  184.                 found_devices << device.deviceNetworkId.split(":")[2].trim()
  185.             }
  186.         }
  187.         section("") {
  188.             if (found_devices.size()) {
  189.                 input "input_cid_devices", "enum", required: false, multiple: true, options: found_devices, title: titles("input_cid_devices"), description: descriptions("input_cid_devices"), submitOnChange: true
  190.                 if (input_cid_devices) {
  191.                     href("pageRemoveSelectedCid", required: false, title: titles("page_remove_selected_cid"), description: descriptions("href_remove_selected_cid"), submitOnChange: true)
  192.                 }
  193.             }
  194.         }
  195.         section("") {
  196.             href("pageAddNewCid", required: false, title: titles("page_add_new_cid"), description: descriptions("href_add_new_cid"))
  197.         }
  198.         section_no_save_note()
  199.     }
  200. }
  201.  
  202. /**
  203.  * Page page_remove_selected_cid generator
  204.  */
  205. def page_remove_selected_cid() {
  206.     def errors = []
  207.     getAllChildDevices().each { device ->
  208.         if (device.deviceNetworkId.contains(":CID-"))
  209.         {
  210.             // Only remove the one that matches our list
  211.             def device_name = device.deviceNetworkId.split(":")[2].trim()
  212.             def d = input_cid_devices.find{ it == device_name }
  213.             if (d)
  214.             {
  215.                 log.trace("removing CID device ${device.deviceNetworkId}")
  216.                 try {
  217.                     deleteChildDevice(device.deviceNetworkId)
  218.                     input_cid_devices.remove(device_name)
  219.                     errors << "Success removing " + device_name                    
  220.                 }
  221.                 catch (e) {
  222.                     log.error "There was an error (${e}) when trying to delete the child device"
  223.                     errors << "Error removing " + device_name
  224.                 }
  225.             }
  226.         }
  227.     }
  228.     return dynamicPage(name: "page_remove_selected_cid") {
  229.         section("") {
  230.             paragraph titles("info_page_remove_selected_cid")
  231.             errors.each { error ->
  232.                 paragraph(error)
  233.             }
  234.         }
  235.     }
  236. }
  237.  
  238. /**
  239.  * Page page_add_new_cid generator
  240.  */
  241. def page_add_new_cid() {
  242.     // list of some of the CID #'s and descriptions.
  243.     // 000 will trigger a manual input of the CID number.
  244.     def cid_numbers = [  "0": "000 - Other / Custom",
  245.                        "101": "101 - Pendant Transmitter",
  246.                        "110": "110 - Fire",
  247.                        "150": "150 - 24 HOUR (AUXILIARY)",
  248.                        "154": "154 - Water Leakage",
  249.                        "158": "158 - High Temp",
  250.                        "162": "162 - Carbon Monoxide Detected",
  251.                        "401": "401 - Arm AWAY OPEN/CLOSE",
  252.                        "441": "441 - Arm STAY OPEN/CLOSE",
  253.                        "4[0,4]1": "4[0,4]1 - Arm Stay or Away OPEN/CLOSE"]
  254.    
  255.     return dynamicPage(name: "page_add_new_cid") {
  256.         // show pre defined CID number templates to select from
  257.         section("") {
  258.             paragraph titles("section_build_cid", buildcid())
  259.             input "input_cid_number", "enum", required: true, submitOnChange: true, multiple: false, title: titles("input_cid_number"), description: descriptions("input_cid_number"), options: cid_numbers
  260.         }
  261.         // if a CID entry is selected then check the value if it is "0" to show raw input section
  262.         if (input_cid_number) {
  263.             if (input_cid_number == "0") {
  264.                 section {
  265.                     input(name: "input_cid_number_raw", type: "text", required: true, defaultValue: 110, submitOnChange: true, title: titles("input_cid_number_raw"))
  266.                 }
  267.             }
  268.             section {
  269.                 paragraph titles("section_cid_value", buildcidvalue())
  270.                 input(name: "input_cid_value", type: "text", required: true, defaultValue: 001,submitOnChange: true, title: titles("input_cid_value"))
  271.             }
  272.             section {
  273.                 paragraph titles("section_cid_partition")
  274.                 input(name: "input_cid_partition", type: "number", required: false, defaultValue: 1, submitOnChange: true, title: titles("input_cid_partition"))
  275.             }
  276.             section {
  277.                 paragraph titles("section_cid_names")
  278.                 input(name: "input_cid_name", type: "text", required: false, submitOnChange: true, title: titles("input_cid_name"))
  279.                 input(name: "input_cid_label", type: "text", required: false, submitOnChange: true, title: titles("input_cid_label"))
  280.             }
  281.             // If input_cid_number or input_cid_number_raw have a value
  282.             if ( (input_cid_number && (input_cid_number != "0")) || (input_cid_number_raw)) {
  283.                 section(""){ href("pageAddNewCidConfirm", required: false, title: titles("page_add_new_cid_confirm", buildcidlabel()+"("+buildcidnetworkid()+")"), description: descriptions("href_add_new_cid_confirm"))}
  284.             }
  285.             section_no_save_note()
  286.         }
  287.     }
  288. }
  289.  
  290. /**
  291.  * Helper to build a final CID value from inputs
  292.  */
  293. def buildcid() {
  294.     def cidnum = ""
  295.     if (input_cid_number == "0") {
  296.         cidnum = input_cid_number_raw
  297.     } else {
  298.         cidnum = input_cid_number
  299.     }
  300.     return cidnum
  301. }
  302.  
  303. def buildcidname() {
  304.     if (input_cid_name) {
  305.         return "CID-" + input_cid_name
  306.     } else {
  307.         return buildcidnetworkid()
  308.     }
  309. }
  310.  
  311. def buildcidlabel() {
  312.     if (input_cid_label) {
  313.         return "CID-" + input_cid_label
  314.     } else {
  315.         return buildcidnetworkid()
  316.     }
  317. }
  318.  
  319. def buildcidnetworkid() {
  320.     // get the CID value
  321.     def newcid =  buildcid()
  322.  
  323.     def cv = buildcidvalue()
  324.     def pt = input_cid_partition
  325.     return "CID-${newcid}-${pt}-${cv}"
  326. }
  327.  
  328. def buildcidvalue() {
  329.     def cidval = input_cid_value
  330.     return cidval
  331. }
  332.  
  333. /**
  334.  * Page page_add_new_cid_confirm generator.
  335.  */
  336. def page_add_new_cid_confirm() {
  337.     def errors = []
  338.     // get the CID value
  339.     def newcidlabel =  buildcidlabel()
  340.     def newcidname = buildcidname()
  341.     def newcidnetworkid = buildcidnetworkid()
  342.     def cv = input_cid_value
  343.     def pt = input_cid_partition.toInteger()
  344.    
  345.     // Add virtual CID switch if it does not exist.
  346.     def d = getChildDevice("${state.ip}:${state.port}:${newcidlabel}")
  347.     if (!d)
  348.     {
  349.         def nd = addChildDevice("alarmdecoder", "AlarmDecoder action button indicator", "${state.ip}:${state.port}:${newcidnetworkid}", state.hub,
  350.                                 [name: "${state.ip}:${state.port}:${newcidname}", label: "${sname} ${newcidlabel}", completedSetup: true])
  351.         nd.sendEvent(name: "switch", value: "off", isStateChange: true, displayed: false)
  352.         errors << "Success adding ${newcidlabel}"
  353.     } else {
  354.         errors << "Error adding ${newcidlabel}: Exists"
  355.     }
  356.  
  357.     return dynamicPage(name: "page_add_new_cid_confirm") {
  358.         section("") {
  359.             paragraph titles("info_add_new_cid_confirm")
  360.             errors.each { error ->
  361.                 paragraph(error)
  362.             }            
  363.         }
  364.     }
  365. }
  366.  
  367. /**
  368.  * Page page_remove_all generator.
  369.  */
  370. def page_remove_all(params) {
  371.     def message = ""
  372.  
  373.     return dynamicPage(name: "page_remove_all") {
  374.         if (params?.confirm) {
  375.             uninstalled()
  376.             message = titles("info_remove_all_a")
  377.         } else {
  378.             section("") {
  379.                 href("pageRemoveAll", title: titles("confirm_remove_all"), description: descriptions("href_refresh_devices"), required: false, params: [confirm: true])
  380.             }
  381.             message = titles("info_remove_all_b")
  382.         }
  383.         section("") {
  384.             paragraph message
  385.         }
  386.     }
  387. }
  388.  
  389. /**
  390.  * Page page_discover_devices generator. Called periodically to refresh content of the page.
  391.  */
  392. def page_discover_devices() {
  393.  
  394.     // send out UPNP discovery messages and watch for responses
  395.     discover_alarmdecoder()
  396.  
  397.     // build list of currently known AlarmDecoder parent devices
  398.     def found_devices = [:]
  399.     def options = state.devices.each { k, v ->
  400.         if (debug) log.debug "page_discover_devices: ${v}"
  401.         def ip = convertHexToIP(v.ip)
  402.         found_devices["${v.ip}:${v.port}"] = "AlarmDecoder @ ${ip}"
  403.     }
  404.  
  405.     // How many do we have?
  406.     def numFound = found_devices.size() ?: 0
  407.  
  408.     return dynamicPage(name: "page_discover_devices") {
  409.         section("Devices") {
  410.             input "input_selected_devices", "enum", required: true, title: titles("input_selected_devices",numFound), multiple: true, options: found_devices
  411.             // Allow user to force a new UPNP discovery message
  412.             href("pageDiscoverDevices", title: titles("href_refresh_devices"), description: descriptions("href_refresh_devices"), required: false)
  413.         }
  414.         section("Smart Home Monitor Integration") {
  415.             input(name: "shmIntegration", type: "bool", defaultValue: true, title: titles("shmIntegration"))
  416.             input(name: "shmChangeSHMStatus", type: "bool", defaultValue: true, title: titles("shmChangeSHMStatus"))
  417.         }
  418.         section("Zone Sensors") {
  419.             input(name: "defaultSensorToClosed", type: "bool", defaultValue: true, title: titles("defaultSensorToClosed"))
  420.         }
  421.     }
  422. }
  423.  
  424. /*** Pre-Defined callbacks ***/
  425.  
  426. /**
  427.  *  installed()
  428.  */
  429. def installed() {
  430.     log.trace "installed"
  431.     if (debug) log.debug "Installed with settings: ${settings}"
  432.  
  433.     // initialize everything
  434.     initialize()
  435. }
  436.  
  437. /**
  438.  * updated()
  439.  */
  440. def updated() {
  441.     log.trace "updated"
  442.     if (debug) log.debug "Updated with settings: ${settings}"
  443.  
  444.     // re initialize everything
  445.     initialize()
  446. }
  447.  
  448. /**
  449.  * uninstalled()
  450.  */
  451. def uninstalled() {
  452.     log.trace "uninstalled"
  453.  
  454.     // disable all scheduling and subscriptions
  455.     unschedule()
  456.  
  457.     // remove all the devices and children
  458.     def devices = getAllChildDevices()
  459.     devices.each {
  460.         try {
  461.             log.debug "deleting child device: ${it.deviceNetworkId}"
  462.             deleteChildDevice(it.deviceNetworkId)
  463.         }
  464.         catch(Exception e) {
  465.             log.trace("exception while uninstalling: ${e}")
  466.         }
  467.     }
  468. }
  469.  
  470. /**
  471.  * initialize called upon update and at startup
  472.  *   Add subscriptions and schdules
  473.  *   Create our default state
  474.  *
  475.  */
  476. def initialize() {
  477.     log.trace "initialize"
  478.  
  479.     // unsubscribe from everything
  480.     unsubscribe()
  481.  
  482.     // remove all schedules
  483.     unschedule()
  484.  
  485.     // Create our default state values
  486.     state.lastSHMStatus = null
  487.     state.lastAlarmDecoderStatus = null
  488.  
  489.     // Network and SHM subscriptions
  490.     initSubscriptions()
  491.  
  492.     // if a device in the GUI is selected then add it.
  493.     if (input_selected_devices) {
  494.         addExistingDevices()
  495.     }
  496.  
  497.     // Device handler -> service subscriptions
  498.     configureDeviceSubscriptions()
  499.  
  500.     // keep us subscribed to notifications
  501.     getAllChildDevices().each { device ->
  502.         // Only refresh the main device that has a panel_state
  503.         def device_type = device.getTypeName()
  504.         if (device_type == "AlarmDecoder network appliance")
  505.         {
  506.             if (debug) log.debug("initialize: Found device refresh subscription.")
  507.             device.subscribeNotifications()
  508.         }
  509.     }
  510. }
  511.  
  512. /*** Handlers ***/
  513.  
  514. /**
  515.  * locationHandler(evt)
  516.  * Local network messages sent to TCP port 39500 and UPNP UDP port 1900 will be captured here.
  517.  *
  518.  * Test from the AlarmDecoder Appliance:
  519.  *   curl -H "Content-Type: application/json" -X POST -d ‘{"message":"Hi, this is a test from AlarmDecoder network device"}’ http://YOUR.HUB.IP.ADDRESS:39500
  520.  */
  521. def locationHandler(evt) {
  522.     if (debug) log.debug "locationHandler"
  523.  
  524.     def description = evt.description
  525.     def hub = evt?.hubId
  526.  
  527.     // many events but we only want PUSH notifications and they have all the data in evt.description
  528.     if (!description)
  529.      return
  530.  
  531.     if (debug)
  532.       log.debug "locationHandler: description: ${description} name: ${evt.name} value: ${evt.value} data: ${evt.data}"
  533.  
  534.     def parsedEvent = ["hub":hub]
  535.     try {
  536.         parsedEvent << parseEventMessage(description)
  537.     }
  538.     catch(Exception e) {
  539.         log.error("exception in parseEventMessage: evt: ${evt.name}: ${evt.value} : ${evt.data}")
  540.         return
  541.     }
  542.  
  543.     if (debug) log.debug("locationHandler parsedEvent: ${parsedEvent}")
  544.  
  545.     // UPNP LAN EVENTS on UDP port 1900 from 'AlarmDecoder:1' devices only
  546.     if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:AlarmDecoder:1")) {
  547.  
  548.         // make sure our state.devices is initialized. return discard.
  549.         def alarmdecoders = getDevices()
  550.  
  551.         // add the device to state.devices if it does not exist yet
  552.         if (!(alarmdecoders."${parsedEvent.ssdpUSN.toString()}")) {
  553.             if (debug) log.debug "locationHandler: Adding device: ${parsedEvent.ssdpUSN}"
  554.             alarmdecoders << ["${parsedEvent.ssdpUSN.toString()}": parsedEvent]
  555.         } else
  556.         { // It exists so update if needed
  557.             // grab the device object based upon ur ssdpUSN
  558.             if (debug) log.debug  "alarmdecoders ${alarmdecoders}"
  559.             def d = alarmdecoders."${parsedEvent.ssdpUSN.toString()}"
  560.  
  561.             if (debug) log.debug "locationHandler: checking for device changed values on device=${d}"
  562.  
  563.             // Did the DNI change? if so update it.
  564.             if (d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
  565.                 // update the state.devices DNI
  566.                 d.ip = parsedEvent.ip
  567.                 d.port = parsedEvent.port
  568.  
  569.                 if (debug) log.debug "locationHandler: device DNI changed values!"
  570.  
  571.                 // Update device by its MAC address if the DNI changes
  572.                 def children = getChildDevices()
  573.                 children.each {
  574.                     if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
  575.                         it.setDeviceNetworkId((parsedEvent.ip + ":" + parsedEvent.port))
  576.                         if (debug) log.debug "Set new network id: " + parsedEvent.ip + ":" + parsedEvent.port
  577.                     }
  578.                 }
  579.             }
  580.  
  581.             // TODO: if the ssdpPath changes we need to fetch a new one
  582.             if (d.ssdpPath != parsedEvent.ssdpPath) {
  583.                 // update the ssdpPath
  584.                 d.ssdpPath = parsedEvent.ssdpPath
  585.                 if (debug) log.debug "locationHandler: device ssdpPath changed values. need to fetch new description.xml."
  586.  
  587.                 // send out reqeusts for xml description for anyone not verified yet
  588.                 // FIXME: verifyAlarmDecoders()
  589.             }
  590.         }
  591.     } else if (parsedEvent?.headers && parsedEvent?.body)
  592.     { // HTTP EVENTS on TCP port 39500 RESPONSES
  593.       // for some reason they hit here and in the parse() in the device?
  594.         def headerString = new String(parsedEvent.headers.decodeBase64())
  595.         def bodyString = new String(parsedEvent.body.decodeBase64())
  596.         def type = (headerString =~ /Content-Type:.*/) ? (headerString =~ /Content-Type:.*/)[0] : null
  597.  
  598.         if (debug) log.debug ("locationHandler HTTP event type:${type} body:${bodyString} headers:${headerString}")
  599.  
  600.         // XML PUSH data
  601.         if (type?.contains("xml"))
  602.         {
  603.             getAllChildDevices().each { device ->
  604.                 // Only refresh the main device that has a panel_state
  605.                 def device_type = device.getTypeName()
  606.                 if (device_type == "AlarmDecoder network appliance")
  607.                 {
  608.                     if (debug) log.debug ("push_update_alarmdecoders: Found device sending pushed data.")
  609.                     device.parse_xml(headerString, bodyString).each { e-> device.sendEvent(e) }
  610.                 }
  611.             }
  612.         }
  613.     }
  614. }
  615.  
  616. /**
  617.  * Handle remote web requests to the ST cloud services for http://somegraph/update
  618.  */
  619. def webserviceUpdate()
  620. {
  621.     log.trace "webserviceUpdate"
  622.     refresh_alarmdecoders()
  623.     return [status: "OK"]
  624. }
  625.  
  626. /**
  627.  * Handle our child device action button events
  628.  * sets Contact attributes of the alarmdecoder smoke device
  629.  */
  630. def actionButton(id) {
  631.     if (debug) log.debug("actionButton: desc=${id}")
  632.  
  633.     // grab our primary AlarmDecoder device object
  634.     def d = getChildDevice("${state.ip}:${state.port}")
  635.     if (!d) {
  636.         log.error("actionButton: Could not find primary device '${state.ip}:${state.port}'.")
  637.         return
  638.     }
  639.  
  640.     /* FIXME: Need a pin code or some way to trust the request.
  641.     if (id.contains(":disarm")) {
  642.         d.disarm()
  643.     } */
  644.  
  645.     if (id.contains(":armAway")) {
  646.         d.arm_away()
  647.     }
  648.     if (id.contains(":armStay")) {
  649.         d.arm_stay()
  650.     }
  651.     if (id.contains(":chimeMode")) {
  652.         d.chime()
  653.     }
  654.     if (id.contains(":alarmPanic")) {
  655.         d.panic()
  656.     }
  657.     if (id.contains(":alarmAUX")) {
  658.         d.aux()
  659.     }
  660.     if (id.contains(":alarmFire")) {
  661.         d.fire()
  662.     }
  663.     if (id.contains(":CID-")) {
  664.         def cd = getChildDevice("${id}")
  665.         if (!cd) {
  666.             log.error("actionButton: Could not CID device '${id}'.")
  667.             return
  668.         }
  669.         cd.sendEvent(name: "switch", value: "off", isStateChange: true, filtered: true)
  670.     }
  671. }
  672.  
  673. /**
  674.  * send event to SmokeAlarm device to set state
  675.  */
  676. def smokeSet(evt) {
  677.     if (debug) log.debug("smokeSet: desc=${evt.value}")
  678.  
  679.     def d = getChildDevices().find { it.deviceNetworkId.contains(":SmokeAlarm") }
  680.     if (!d)
  681.     {
  682.         log.error("smokeSet: Could not find 'SmokeAlarm' device.")
  683.         return
  684.     }
  685.     d.sendEvent(name: "smoke", value: evt.value, isStateChange: true, filtered: true)
  686. }
  687.  
  688. /**
  689.  * send event to armAway device to set state
  690.  */
  691. def armAwaySet(evt) {
  692.     if (debug) log.debug("armAwaySet ${evt.value}")
  693.     def d = getChildDevice("${state.ip}:${state.port}:armAway")
  694.     if (!d) {
  695.         log.error("armAwaySet: Could not find 'armAway' device.")
  696.         return
  697.     }
  698.     d.sendEvent(name: "switch", value: evt.value, isStateChange: true, filtered: true)
  699. }
  700.  
  701. /**
  702.  * send event to armStay device to set state
  703.  */
  704. def armStaySet(evt) {
  705.     if (debug) log.debug("armStaySet ${evt.value}")
  706.     def d = getChildDevice("${state.ip}:${state.port}:armStay")
  707.     if (!d) {
  708.         log.error("armStaySet: Could not find 'armStay' device.")
  709.         return
  710.     }
  711.     d.sendEvent(name: "switch", value: evt.value, isStateChange: true, filtered: true)
  712. }
  713.  
  714. /**
  715.  * send event to alarmbell indicator device to set state
  716.  */
  717. def alarmBellSet(evt) {
  718.     if (debug)
  719.       log.debug("alarmBellSet ${evt.value}")
  720.     def d = getChildDevice("${state.ip}:${state.port}:alarmBellIndicator")
  721.     if (!d) {
  722.         log.error("alarmBellSet: Could not find 'alarmBellIndicator' device.")
  723.         return
  724.     }
  725.     d.sendEvent(name: "contact", value: evt.value, isStateChange: true, filtered: true)
  726. }
  727.  
  728. /**
  729.  * send event to chime indicator device to set state
  730.  */
  731. def chimeSet(evt) {
  732.     if (debug) log.debug("chimeSet ${evt.value}")
  733.     def d = getChildDevice("${state.ip}:${state.port}:chimeMode")
  734.     if (!d) {
  735.         log.error("chimeSet: Could not find device 'chimeMode'")
  736.         return
  737.     }
  738.     d.sendEvent(name: "switch", value: evt.value, isStateChange: true, filtered: true)
  739. }
  740.  
  741. /**
  742.  * send event to bypass indicator device to set state
  743.  */
  744. def bypassSet(evt) {
  745.     if (debug) log.debug("bypassSet ${evt.value}")
  746.     def d = getChildDevice("${state.ip}:${state.port}:bypass")
  747.     if (!d) {
  748.         log.error("bypassSet: Could not find device 'bypass'")
  749.         return
  750.     }
  751.     d.sendEvent(name: "contact", value: evt.value, isStateChange: true, filtered: true)
  752. }
  753.  
  754. /**
  755.  * send event to ready indicator device to set state
  756.  */
  757. def readySet(evt) {
  758.     if (debug) log.debug("readySet ${evt.value}")
  759.     def d = getChildDevice("${state.ip}:${state.port}:readyIndicator")
  760.     if (!d) {
  761.         log.error("readySet: Could not find 'readyIndicator' device.")
  762.         return
  763.     }
  764.     d.sendEvent(name: "contact", value: evt.value, isStateChange: true, filtered: true)
  765. }
  766.  
  767. /**
  768.  * send CID event to the correct device if one exists
  769.  * evt.value example !LRR:001,1,CID_1406,ff
  770.  */
  771. def cidSet(evt) {
  772.     log.info("cidSet ${evt.value}")
  773.  
  774.     // get our CID state and number
  775.     def parts = evt.value.split(',')
  776.  
  777.     // 1 digit QUALIFIER 1 = Event or Open, 3 = Restore or Close  
  778.     def cidstate = (parts[2][-4..-4] == "1") ? "on" : "off"
  779.  
  780.     // 3 digit CID number
  781.     def cidnum = parts[2][-3..-1]
  782.  
  783.     // the CID report value. Zone # or User # or ...
  784.     def cidvalue = parts[0].split(':')[1]
  785.  
  786.     // the partition # with 0 being system
  787.     def partition =  parts[1].toInteger()
  788.  
  789.     if (debug) log.debug("cidSet num:${cidnum} part: ${partition} state:${cidstate} val:${cidvalue}")
  790.  
  791.     def sent = false
  792.     def rawmsg = evt.value
  793.     def device_name = "CID-${cidnum}-${partition}-${cidvalue}"
  794.     def children = getChildDevices()
  795.     children.each {
  796.         if (it.deviceNetworkId.contains(":CID-")) {
  797.             def match = it.deviceNetworkId.split(":")[2].trim()
  798.             if (device_name =~ /${match}/) {
  799.                 if (debug) log.error("cidSet device: ${device_name} matches ${match} sendng state ${cidstate}")
  800.                 it.sendEvent(name: "switch", value: cidstate, isStateChange: true, filtered: true)
  801.                 sent = true
  802.             } else {
  803.                 if (debug) log.error("cidSet device: ${device_name} no match ${match}")
  804.             }
  805.         }
  806.     }
  807.  
  808.     if (!sent) {
  809.         log.error("cidSet: Could not find 'CID-${cidnum}-${partition}-${cidvalue}|XXX' device.")
  810.         return
  811.     }
  812. }
  813.  
  814. /**
  815.  * Handle Device Command zoneOn()
  816.  * sets Contact attributes of the alarmdecoder device to open/closed
  817.  */
  818. def zoneOn(evt) {
  819.     if (debug) log.debug("zoneOn: desc=${evt.value}")
  820.  
  821.     def d = getChildDevices().find { it.deviceNetworkId.endsWith("switch${evt.value}") }
  822.     if (d)
  823.     {
  824.         def sensorValue = "closed"
  825.         if (settings.defaultSensorToClosed == true)
  826.             sensorValue = "open"
  827.  
  828.         d.sendEvent(name: "contact", value: sensorValue, isStateChange: true, filtered: true)
  829.     }
  830. }
  831.  
  832. /**
  833.  * Handle Device Command zoneOff()
  834.  * sets Contact attributes of the alarmdecoder device to open/closed
  835.  */
  836. def zoneOff(evt) {
  837.     if (debug) log.debug("zoneOff: desc=${evt.value}")
  838.  
  839.     def d = getChildDevices().find { it.deviceNetworkId.endsWith("switch${evt.value}") }
  840.     if (d)
  841.     {
  842.         def sensorValue = "open"
  843.         if (settings.defaultSensorToClosed == true)
  844.             sensorValue = "closed"
  845.  
  846.         d.sendEvent(name: "contact", value: sensorValue, isStateChange: true, filtered: true)
  847.     }
  848. }
  849.  
  850. /**
  851.  * Handle Smart Home Monitor App alarmSystemStatus events and update the UI of the App.
  852.  *
  853.  */
  854. def shmAlarmHandler(evt) {
  855.     if (settings.shmIntegration == false)
  856.         return
  857.  
  858.     if (debug) log.debug("shmAlarmHandler -- ${evt.value}, lastSHMStatus ${state.lastSHMStatus}, lastAlarmDecoderStatus ${state.lastAlarmDecoderStatus}")
  859.  
  860.     if (state.lastSHMStatus != evt.value && evt.value != state.lastAlarmDecoderStatus)
  861.     {
  862.         getAllChildDevices().each { device ->
  863.             if (!device.deviceNetworkId.contains(":switch"))
  864.             {
  865.                 if (evt.value == "away")
  866.                     device.arm_away()
  867.                 else if (evt.value == "stay")
  868.                     device.arm_stay()
  869.                 else if (evt.value == "off")
  870.                     device.disarm()
  871.                 else
  872.                     log.debug "Unknown SHM alarm value: ${evt.value}"
  873.             }
  874.         }
  875.     }
  876.  
  877.     state.lastSHMStatus = evt.value
  878. }
  879.  
  880. /**
  881.  * Handle Alarm events from the AlarmDecoder and
  882.  * send them back to the Smart Home Monitor API to update the
  883.  * status of the alarm panel
  884.  */
  885. def alarmdecoderAlarmHandler(evt) {
  886.     if (settings.shmIntegration == false || settings.shmChangeSHMStatus == false)
  887.         return
  888.  
  889.     if (debug) log.trace("alarmdecoderAlarmHandler: ${evt.value}")
  890.  
  891.     if (state.lastAlarmDecoderStatus != evt.value && evt.value != state.lastSHMStatus) {
  892.         if (debug) log.debug("alarmdecoderAlarmHandler: sendLocationEvent")
  893.         sendLocationEvent(name: "alarmSystemStatus", value: evt.value)
  894.     }
  895.  
  896.     state.lastAlarmDecoderStatus = evt.value
  897. }
  898.  
  899. /*** Utility ***/
  900.  
  901. /**
  902.  * Enable primary network and system subscriptions
  903.  */
  904. def initSubscriptions() {
  905.     // subscribe to the Smart Home Manager api for alarm status events
  906.     if (debug) log.debug("initialize: subscribe to SHM alarmSystemStatus API messages")
  907.     subscribe(location, "alarmSystemStatus", shmAlarmHandler)
  908.  
  909.     /* subscribe to local LAN messages to this HUB on TCP port 39500 and UPNP UDP port 1900 */
  910.     if (debug) log.debug("initialize: subscribe to locations local LAN messages")
  911.     subscribe(location, "ssdpTerm", locationHandler, [filterEvents: false])
  912. }
  913.  
  914. /**
  915.  * Called by page_discover_devices page periodically
  916.  */
  917. def discover_alarmdecoder() {
  918.     if (debug) log.debug("discover_alarmdecoder")
  919.  
  920.     // Request HUB send out a UpNp broadcast discovery messages on the local network
  921.     sendHubCommand(new hubitat.device.HubAction("lan discovery urn:schemas-upnp-org:device:AlarmDecoder:1", hubitat.device.Protocol.LAN))
  922. }
  923.  
  924. /**
  925.  * Call refresh() on the AlarmDecoder parent device object.
  926.  * This will force the HUB to send a REST API request to the AlarmDecoder Network Appliance.
  927.  * and get back the current status of the AlarmDecoder.
  928.  */
  929. def refresh_alarmdecoders() {
  930.     if (debug) log.debug("refresh_alarmdecoders")
  931.  
  932.     getAllChildDevices().each { device ->
  933.         // Only refresh the main device that has a panel_state
  934.         def device_type = device.getTypeName()
  935.         if (device_type == "AlarmDecoder network appliance")
  936.         {
  937.             def apikey = device._get_api_key()
  938.             if (apikey) {
  939.                 device.refresh()
  940.             } else {
  941.                 log.error("refresh_alarmdecoders no API KEY for: ${device} @ ${device.getDataValue("urn")}")
  942.             }
  943.         }
  944.     }
  945. }
  946.  
  947. /**
  948.  * return the list of known devices and initialize the list if needed.
  949.  *
  950.  * FIXME: SM20180315:
  951.  *        This uses the ssdpUSN as the key when we also use DNI
  952.  *        Why not just use DNI all over or ssdpUSN. Keep it consistent.
  953.  *        We get ssdpUSN from our UPNP discovery messages on port 1900
  954.  *        and then we get DNI messages from our GET requests to the
  955.  *        alarmdecoder web services on port 5000. We can also get DNI
  956.  *        from Notification events we subscribe to when the AlarmDecoder
  957.  *        sends us requests on port 39500. Easy way is to use DNI as we get
  958.  *        it every time from all requests. Downside is we can not have more
  959.  *        than one AlarmDecoder per IP:PORT. This seems ok to me for now.
  960.  *
  961.  *
  962.  *  state.devices structure
  963.  *  [
  964.  *      uuid:0c510e98-8ce0-11e7-81a5-XXXXXXXXXXXXXX:
  965.  *      [
  966.  *          port:1388,
  967.  *          ssdpUSN:uuid:0c510e98-8ce0-11e7-81a5-XXXXXXXXXXXXXX,
  968.  *          devicetype:04,
  969.  *          mac:XXXXXXXXXX02,
  970.  *          hub:936de0be-1cb7-4185-9ac9-XXXXXXXXXXXXXX,
  971.  *          ssdpPath:http://XXX.XXX.XXX.XXX:5000,
  972.  *          ssdpTerm:urn:schemas-upnp-org:device:AlarmDecoder:1,
  973.  *          ip:XXXXXXX2
  974.  *      ],
  975.  *      uuid:592952ba-77b0-11e7-b0c7-XXXXXXXXXXXXXX:
  976.  *      [
  977.  *          port:1388,
  978.  *          ssdpUSN:uuid:592952ba-77b0-11e7-b0c7-XXXXXXXXXXXXXX,
  979.  *          devicetype:04,
  980.  *          mac:XXXXXXXXXX01,
  981.  *          hub:936de0be-1cb7-4185-9ac9-XXXXXXXXXXXXXX,
  982.  *          ssdpPath:/static/device_description.xml,
  983.  *          ssdpTerm:urn:schemas-upnp-org:device:AlarmDecoder:1,
  984.  *          ip:XXXXXXX1
  985.  *      ]
  986.  *  ]
  987.  *
  988.  */
  989. def getDevices() {
  990.     if (!state.devices) {
  991.         state.devices = [:]
  992.     }
  993.     return state.devices
  994. }
  995.  
  996. /**
  997.  * Add devices selected in the GUI if new.
  998.  */
  999. def addExistingDevices() {
  1000.     if (debug) log.debug("addExistingDevices: ${input_selected_devices}")
  1001.  
  1002.     def selected_devices = input_selected_devices
  1003.     if (selected_devices instanceof java.lang.String) {
  1004.         selected_devices = [selected_devices]
  1005.     }
  1006.  
  1007.     selected_devices.each { dni ->
  1008.         def d = getChildDevice(dni)
  1009.         if (debug) log.debug("addExistingDevices, getChildDevice(${dni})")
  1010.         if (!d) {
  1011.  
  1012.             // Find the device with a matching dni XXXXXXXX:XXXX
  1013.             def newDevice = state.devices.find { /*k, v -> k == dni*/ k, v -> dni == "${v.ip}:${v.port}" }
  1014.             if (debug) log.debug("addExistingDevices, devices.find=${newDevice}")
  1015.  
  1016.             if (newDevice) {
  1017.                 // Set the device network ID so that hubactions get sent to the device parser.
  1018.                 state.ip = newDevice.value.ip
  1019.                 state.port = newDevice.value.port
  1020.                 state.hub = newDevice.value.hub
  1021.  
  1022.                 // Set URN for the child device
  1023.                 state.urn = convertHexToIP(state.ip) + ":" + convertHexToInt(state.port)
  1024.                 if (debug) log.debug("AlarmDecoder webapp api endpoint('${state.urn}')")
  1025.  
  1026.                 // Create device adding the URN to its data object
  1027.                 d = addChildDevice("alarmdecoder",
  1028.                                    "AlarmDecoder network appliance",
  1029.                                    "${state.ip}:${state.port}",
  1030.                                    newDevice?.value.hub,
  1031.                                    [
  1032.                                        name: "${state.ip}:${state.port}",
  1033.                                        label: "${lname}(${sname})",
  1034.                                        completedSetup: true,
  1035.                                        /* data associated with this AlarmDecoder */
  1036.                                        data:[
  1037.                                                 // save mac address to update if IP / PORT change
  1038.                                                 mac: newDevice.value.mac,
  1039.                                                 ssdpUSN: newDevice.value.ssdpUSN,
  1040.                                                 urn: state.urn,
  1041.                                                 ssdpPath: newDevice.value.ssdpPath
  1042.                                             ]
  1043.                                    ])
  1044.  
  1045.                 // Set default device state to notready.
  1046.                 d.sendEvent(name: "panel_state", value: "notready", isStateChange: true, displayed: true)
  1047.  
  1048.             }
  1049.             // Add virtual zone contact sensors if they do not exist.
  1050.             for (def i = 0; i < 12; i++)
  1051.             {
  1052.                 def newSwitch = state.devices.find { k, v -> k == "${state.ip}:${state.port}:switch${i+1}" }
  1053.                 if (!newSwitch)
  1054.                 {
  1055.                     def zone_switch = addChildDevice("alarmdecoder", "AlarmDecoder virtual contact sensor", "${state.ip}:${state.port}:switch${i+1}", state.hub, [name: "${state.ip}:${state.port}:switch${i+1}", label: "${sname} Zone Sensor #${i+1}", completedSetup: true])
  1056.  
  1057.                     def sensorValue = "open"
  1058.                     if (settings.defaultSensorToClosed == true)
  1059.                         sensorValue = "closed"
  1060.  
  1061.                     // Set default contact state.
  1062.                     zone_switch.sendEvent(name: "contact", value: sensorValue, isStateChange: true, displayed: false)
  1063.                 }
  1064.             }
  1065.  
  1066.             // Add virtual Smoke Alarm sensors if it does not exist.
  1067.             def cd = state.devices.find { k, v -> k == "${state.ip}:${state.port}:SmokeAlarm" }
  1068.             if (!cd)
  1069.             {
  1070.                 def nd = addChildDevice("alarmdecoder", "AlarmDecoder virtual smoke alarm", "${state.ip}:${state.port}:SmokeAlarm", state.hub,
  1071.                 [name: "${state.ip}:${state.port}:smokeAlarm", label: "${sname} Smoke Alarm", completedSetup: true])
  1072.                 nd.sendEvent(name: "smoke", value: "clear", isStateChange: true, displayed: false)
  1073.             }
  1074.  
  1075.             // Add virtual Arm Stay switch if it does not exist.
  1076.             cd = state.devices.find { k, v -> k == "${state.ip}:${state.port}:armStay" }
  1077.             if (!cd)
  1078.             {
  1079.                 def nd = addChildDevice("alarmdecoder", "AlarmDecoder action button indicator", "${state.ip}:${state.port}:armStay", state.hub,
  1080.                 [name: "${state.ip}:${state.port}:armStay", label: "${sname} Stay", completedSetup: true])
  1081.                 nd.sendEvent(name: "switch", value: "off", isStateChange: true, displayed: false)
  1082.             }
  1083.  
  1084.             // Add virtual Arm Away switch if it does not exist.
  1085.             cd = state.devices.find { k, v -> k == "${state.ip}:${state.port}:armAway" }
  1086.             if (!cd)
  1087.             {
  1088.                 def nd = addChildDevice("alarmdecoder", "AlarmDecoder action button indicator", "${state.ip}:${state.port}:armAway", state.hub,
  1089.                 [name: "${state.ip}:${state.port}:armAway", label: "${sname} Away", completedSetup: true])
  1090.                 nd.sendEvent(name: "switch", value: "off", isStateChange: true, displayed: false)
  1091.             }
  1092.  
  1093.             // Add virtual Chime switch if it does not exist.
  1094.             cd = state.devices.find { k, v -> k == "${state.ip}:${state.port}:chimeMode" }
  1095.             if (!cd)
  1096.             {
  1097.                 def nd = addChildDevice("alarmdecoder", "AlarmDecoder action button indicator", "${state.ip}:${state.port}:chimeMode", state.hub,
  1098.                 [name: "${state.ip}:${state.port}:chimeMode", label: "${sname} Chime", completedSetup: true])
  1099.                 nd.sendEvent(name: "switch", value: "off", isStateChange: true, displayed: false)
  1100.             }
  1101.  
  1102.             // Add virtual Bypass switch if it does not exist.
  1103.             cd = state.devices.find { k, v -> k == "${state.ip}:${state.port}:bypass" }
  1104.             if (!cd)
  1105.             {
  1106.                 def nd = addChildDevice("alarmdecoder", "AlarmDecoder status indicator", "${state.ip}:${state.port}:bypass", state.hub,
  1107.                 [name: "${state.ip}:${state.port}:bypass", label: "${sname} Bypass", completedSetup: true])
  1108.                 nd.sendEvent(name: "contact", value: "close", isStateChange: true, displayed: false)
  1109.             }
  1110.  
  1111.             // Add virtual Ready contact if it does not exist.
  1112.             cd = state.devices.find { k, v -> k == "${state.ip}:${state.port}:readyIndicator" }
  1113.             if (!cd)
  1114.             {
  1115.                 def nd = addChildDevice("alarmdecoder", "AlarmDecoder status indicator", "${state.ip}:${state.port}:readyIndicator", state.hub,
  1116.                 [name: "${state.ip}:${state.port}:readyIndicator", label: "${sname} Ready", completedSetup: true])
  1117.                 nd.sendEvent(name: "contact", value: "close", isStateChange: true, displayed: false)
  1118.             }
  1119.  
  1120.             // Add virtual Alarm Bell contact if it does not exist.
  1121.             cd = state.devices.find { k, v -> k == "${state.ip}:${state.port}:alarmBell" }
  1122.             if (!cd)
  1123.             {
  1124.                 def nd = addChildDevice("alarmdecoder", "AlarmDecoder status indicator", "${state.ip}:${state.port}:alarmBellIndicator", state.hub,
  1125.                 [name: "${state.ip}:${state.port}:alarmBellIndicator", label: "${sname} Alarm Bell", completedSetup: true])
  1126.                 nd.sendEvent(name: "contact", value: "close", isStateChange: true, displayed: false)
  1127.             }
  1128.  
  1129.             // Add FIRE alarm button if it does not exist.
  1130.             cd = state.devices.find { k, v -> k == "${state.ip}:${state.port}:alarmFire" }
  1131.             if (!cd)
  1132.             {
  1133.                 def nd = addChildDevice("alarmdecoder", "AlarmDecoder action button indicator", "${state.ip}:${state.port}:alarmFire", state.hub,
  1134.                 [name: "${state.ip}:${state.port}:alarmFire", label: "${sname} Fire Alarm", completedSetup: true])
  1135.                 nd.sendEvent(name: "switch", value: "close", isStateChange: true, displayed: false)
  1136.             }
  1137.  
  1138.             // Add Panic alarm button if it does not exist.
  1139.             cd = state.devices.find { k, v -> k == "${state.ip}:${state.port}:alarmPanic" }
  1140.             if (!cd)
  1141.             {
  1142.                 def nd = addChildDevice("alarmdecoder", "AlarmDecoder action button indicator", "${state.ip}:${state.port}:alarmPanic", state.hub,
  1143.                 [name: "${state.ip}:${state.port}:alarmPanic", label: "${sname} Panic Alarm", completedSetup: true])
  1144.                 nd.sendEvent(name: "switch", value: "close", isStateChange: true, displayed: false)
  1145.             }
  1146.  
  1147.             // Add AUX alarm button if it does not exist.
  1148.             cd = state.devices.find { k, v -> k == "${state.ip}:${state.port}:alarmAUX" }
  1149.             if (!cd)
  1150.             {
  1151.                 def nd = addChildDevice("alarmdecoder", "AlarmDecoder action button indicator", "${state.ip}:${state.port}:alarmAUX", state.hub,
  1152.                 [name: "${state.ip}:${state.port}:alarmAUX", label: "${sname} AUX Alarm", completedSetup: true])
  1153.                 nd.sendEvent(name: "switch", value: "close", isStateChange: true, displayed: false)
  1154.             }
  1155.         }
  1156.     }
  1157. }
  1158.  
  1159. /**
  1160.  * Configure subscriptions the virtual devices will send too.
  1161.  */
  1162. private def configureDeviceSubscriptions() {
  1163.     if (debug) log.debug("configureDeviceSubscriptions")
  1164.     def device = getChildDevice("${state.ip}:${state.port}")
  1165.     if (!device) {
  1166.         log.error("configureDeviceSubscriptions: Could not find primary device.")
  1167.         return
  1168.     }
  1169.  
  1170.     /* Handle events sent from the AlarmDecoder network appliance device
  1171.      * to update virtual zones when they change.
  1172.      */
  1173.     subscribe(device, "zone-on", zoneOn, [filterEvents: false])
  1174.     subscribe(device, "zone-off", zoneOff, [filterEvents: false])
  1175.  
  1176.     /* Subscribe to Smart Home Monitor(SHM) alarmStatus events
  1177.      */
  1178.     subscribe(device, "alarmStatus", alarmdecoderAlarmHandler, [filterEvents: false])
  1179.  
  1180.     // subscrib to smoke-set handler for updates
  1181.     subscribe(device, "smoke-set", smokeSet, [filterEvents: false])
  1182.  
  1183.     // subscribe to arm-away handler
  1184.     subscribe(device, "arm-away-set", armAwaySet, [filterEvents: false])
  1185.  
  1186.     // subscribe to arm-stay handler
  1187.     subscribe(device, "arm-stay-set", armStaySet, [filterEvents: false])
  1188.  
  1189.     // subscribe to chime handler
  1190.     subscribe(device, "chime-set", chimeSet, [filterEvents: false])
  1191.  
  1192.     // subscribe to bypass handler
  1193.     subscribe(device, "bypass-set", bypassSet, [filterEvents: false])
  1194.  
  1195.     // subscribe to alarm bell handler
  1196.     subscribe(device, "alarmbell-set", alarmBellSet, [filterEvents: false])
  1197.  
  1198.     // subscribe to ready handler
  1199.     subscribe(device, "ready-set", readySet, [filterEvents: false])
  1200.  
  1201.     // subscribe to CID handler
  1202.     subscribe(device, "cid-set", cidSet, [filterEvents: false])
  1203.  
  1204. }
  1205.  
  1206. /**
  1207.  * Parse local network messages.
  1208.  *
  1209.  * May be to UDP port 1900 for UPNP message or to TCP port 39500
  1210.  * for local network to hub push messages.
  1211.  *
  1212.  */
  1213. private def parseEventMessage(String description) {
  1214.     if (debug)
  1215.       log.debug "parseEventMessage: $description"
  1216.     def event = [:]
  1217.     def parts = description.split(',')
  1218.     parts.each { part ->
  1219.         part = part.trim()
  1220.         if (part.startsWith('devicetype:')) {
  1221.             def valueString = part.split(":")[1].trim()
  1222.             event.devicetype = valueString
  1223.         }
  1224.         else if (part.startsWith('mac:')) {
  1225.             def valueString = part.split(":")[1].trim()
  1226.             if (valueString) {
  1227.                 event.mac = valueString
  1228.             }
  1229.         }
  1230.         else if (part.startsWith('networkAddress:')) {
  1231.             def valueString = part.split(":")[1].trim()
  1232.             if (valueString) {
  1233.                 event.ip = valueString
  1234.             }
  1235.         }
  1236.         else if (part.startsWith('deviceAddress:')) {
  1237.             def valueString = part.split(":")[1].trim()
  1238.             if (valueString) {
  1239.                 event.port = valueString
  1240.             }
  1241.         }
  1242.         else if (part.startsWith('ssdpPath:')) {
  1243.             part -= "ssdpPath:"
  1244.             def valueString = part.trim()
  1245.             if (valueString) {
  1246.                 event.ssdpPath = valueString
  1247.             }
  1248.         }
  1249.         else if (part.startsWith('ssdpUSN:')) {
  1250.             part -= "ssdpUSN:"
  1251.             def valueString = part.trim()
  1252.             if (valueString) {
  1253.                 event.ssdpUSN = valueString
  1254.             }
  1255.         }
  1256.         else if (part.startsWith('ssdpTerm:')) {
  1257.             part -= "ssdpTerm:"
  1258.             def valueString = part.trim()
  1259.             if (valueString) {
  1260.                 event.ssdpTerm = valueString
  1261.             }
  1262.         }
  1263.         else if (part.startsWith('headers')) {
  1264.             part -= "headers:"
  1265.             def valueString = part.trim()
  1266.             if (valueString) {
  1267.                 event.headers = valueString
  1268.             }
  1269.         }
  1270.         else if (part.startsWith('body')) {
  1271.             part -= "body:"
  1272.             def valueString = part.trim()
  1273.             if (valueString) {
  1274.                 event.body = valueString
  1275.             }
  1276.         }
  1277.     }
  1278.  
  1279.     event
  1280. }
  1281.  
  1282. /**
  1283.  * Send a request for the description.xml For every known AlarmDecoder
  1284.  * we have discovered that is not verified.
  1285.  */
  1286. def verifyAlarmDecoders() {
  1287.     def devices = getDevices().findAll { it?.value?.verified != true }
  1288.  
  1289.   if (devices) {
  1290.         log.warn "verifyAlarmDecoders: UNVERIFIED Decoders!: $devices"
  1291.   }
  1292.  
  1293.   devices.each {
  1294.         if (it?.value?.ssdpPath?.contains("xml")) {
  1295.             verifyAlarmDecoder((it?.value?.ip + ":" + it?.value?.port), it?.value?.ssdpPath)
  1296.         } else {
  1297.             log.warn("verifyAlarmDecoders: invalid ssdpPath not an xml file")
  1298.         }
  1299.     }
  1300. }
  1301.  
  1302. /**
  1303.  * Send a GET request from the HUB to the AlarmDecoder for its descrption.xml file
  1304.  */
  1305. def verifyAlarmDecoder(String deviceNetworkId, String ssdpPath) {
  1306.   String ip = getHostAddressFromDNI(deviceNetworkId)
  1307.  
  1308.   if (debug) log.debug("verifyAlarmDecoder: $deviceNetworkId ssdpPath: ${ssdpPath} ip: ${ip}")
  1309.  
  1310.   def result = new hubitat.device.HubAction([
  1311.   method: "GET",
  1312.         path: ssdpPath,
  1313.         headers: [Host: ip, Accept: "*/*"]],
  1314.         deviceNetworkId
  1315.   )
  1316.   sendHubCommand(result)
  1317. }
  1318.  
  1319. /**
  1320.  * Convert from internal format networkAddress:C0A8016F to a real IP address string 192.168.1.111
  1321.  */
  1322. private String convertHexToIP(hex) {
  1323.     [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
  1324. }
  1325.  
  1326. /**
  1327.  * convert hex encoded string to integer
  1328.  */
  1329. private Integer convertHexToInt(hex) {
  1330.     Integer.parseInt(hex,16)
  1331. }
  1332.  
  1333. /**
  1334.  * build a URI host address of the AlarmDecoder web appliance for web requests.
  1335.  *  ex. XXX.XXX.XXX.XXX:XXXXX -> 192.168.1.1:5000
  1336.  */
  1337. private getHostAddressFromDNI(d) {
  1338.   def parts = d.split(":")
  1339.   def ip = convertHexToIP(parts[0])
  1340.   def port = convertHexToInt(parts[1])
  1341.   return ip + ":" + port
  1342. }
Add Comment
Please, Sign In to add comment