/** * MIMOlite device type for garage door button, including power failure indicator. Be sure mimolite has jumper removed before * including the device to your hub, and tap Config to ensure power alarm is subscribed. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License * for the specific language governing permissions and limitations under the License. * * Updates: * ------- * 02-18-2016 : Initial commit * 03-05-2016 : Changed date format to MM-dd-yyyy h:mm a * 03-11-2016 : Due to ST's v2.1.0 app totally hosing up SECONDARY_CONTROL, implemented a workaround to display that info in a separate tile. * 08-27-2016 : Modified the device handler for my liking, primarly for looks and feel. * 01-08/2017 : Added code for Health Check capabilities/functions. * 02-11-2017 : Cleaned up code, and used secondary_control again for messages. * 03-11-2017 : Cleaned up code. * 03-24-2017 : Changed color schema to match ST's new format. * 04-08-2017 : Updated the updated() section to call configuration(). * 05-19-2017 : Added additional attributeStates to match ST's DTH which should make this work with ActionTiles, and to use contact as the main tile instead of switch due to personal preference. * 05-20-2017 : Redefined tiles/names to be similar to the Linear-type opener DTH's, which should make this work with ActionTiles. * 05-23-2017 : Made the delay longer for retreiving device info (gets) after the main tile or the refresh tile is tapped. * 09-22-2019 : converted DH to Hubitat and added refresh before each action * 10-07-2019 : removed extra refresh from open/close it caused to much delay * 01-01-2020 : made backwards compatible with stock Hubitat DH * 02-26-2020 : added door open/close timing and software lock for extra security to disable door opening set the momentary switch delay setting as default 30 (3 sec) if you want to disable and manually control the on/off function select 0 (disabled) in device settings * 03-01-2020 : added logic so the DH should not get out of sequence with the garage door and the garage door can not be activated unless fully open or closed and removed "doorstate" as it was redundant * 04-15-2020 : fixed an error in refresh, added auto refresh, and auto close * 05-01-2020 : fixed an error in parsing, made auto close recurring(runEvery cmd time is not accurate more like run every less than), fixed a bug with manually opening door and auto close not firing, fixed the lock function not appearing on dashboard correctly * 06-01-2020 : added automatic secondary door state check using the door timer and refresh * 06-22-2020 : added 30 minute log debug off * 01-01-2021 : added additional auto refresh time options to compensate for faulty SIG1 magnetic dry contact, changed parameterNumber 9 value from 5 to 3 */ metadata { definition (name: "MIMOlite Garage Door Controller 01/01/2021", namespace: "Hubitat", author: "scgs350", modified: "NoWon", importUrl: "https://raw.githubusercontent.com/NoWon69/mimolite/master/mimolite%20garage%20door%20DH") { capability "Momentary" capability "Relay Switch" capability "Polling" capability "Refresh" capability "Switch" capability "Sensor" capability "Contact Sensor" capability "Configuration" capability "Actuator" capability "Door Control" capability "Garage Door Control" capability "Health Check" capability "Lock" attribute "powered", "string" command "setSwitchDelay",[[title:"momentary switch delay", name:"momentary switch delay 0(disabled),1 sec,2 sec(default),3 sec", type:"NUMBER", description:"momentary switch delay", constraints:["NUMBER"]]] fingerprint mfr: "132", deviceId: "0x1000", prod:"1107", inClusters: "0x72,0x86,0x71,0x30,0x31,0x35,0x70,0x85,0x25,0x03", deviceJoinName: "mimolite garage door" } preferences { input "doortimer", "enum", title:"door full open or close Time", required:false, defaultValue:20, options:[0:"disabled", 10:"10 sec", 20:"20 sec (default)", 30:"30 sec", 45:"45 sec", 60:"1 Minutes", 300:"5 Minutes", 600:"10 Minutes"] input name: "logEnable", type: "bool", title: "Enable 30 min debug logging", defaultValue: false input "autorefresh", "enum", title:"auto refresh every?", required:false, defaultValue:0, options:[0:"disabled (default)", 1:"5 minutes", 2:"10 minutes", 3:"15 minutes", 4:"30 minutes", 5:"1 hour", 6:"3 hours"] input "autoclose", "enum", title:"auto close", required:false, defaultValue:0, options:[0:"disabled (default)", 5:"5 minutes", 10:"10 minutes", 15:"15 minutes", 30:"30 minutes", 60:"1 hour", 180:"3 hours"] input name: "doorCheckEnable", type: "bool", title: "refresh after door full open or closed", defaultValue: false input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: false } } def autoClose() { if (autoclose == null) autoClose = 0 else autoClose = Integer.parseInt(autoclose) if (autoClose == 0) { if (txtEnable) log.info "auto close disabled" sendEvent(name: "auto_close", value: "disabled") unschedule(close) } if (autoClose == 5) { if (txtEnable) log.info "auto close 5 minutes" sendEvent(name: "auto_close", value: "5 minutes") runEvery5Minutes(close) } if (autoClose == 10) { if (txtEnable) log.info "auto close 10 minutes" sendEvent(name: "auto_close", value: "10 minutes") runEvery10Minutes(close) } if (autoClose == 15) { if (txtEnable) log.info "auto close 15 minutes" sendEvent(name: "auto_close", value: "15 minutes") runEvery15Minutes(close) } if (autoClose == 30) { if (txtEnable) log.info "auto close 30 minutes" sendEvent(name: "auto_close", value: "30 minutes") runEvery30Minutes(close) } if (autoClose == 60) { if (txtEnable) log.info "auto close 1 hour" sendEvent(name: "auto_close", value: "1 hour") runEvery1Hour(close) } if (autoClose == 180) { if (txtEnable) log.info "auto close 3 hours" sendEvent(name: "auto_close", value: "3 hours") runEvery3Hours(close) } if (device.currentValue("contact") == "closed") { if (txtEnable) log.info "auto close off because door is closed" unschedule(close) } } def autoRefresh() { if (autorefresh == null) autoRefresh = 0 else autoRefresh = Integer.parseInt(autorefresh) if (autoRefresh == 0) { if (txtEnable) log.info "auto refresh disabled" sendEvent(name: "auto_refresh", value: "disabled") unschedule(refresh) } if (autoRefresh == 1) { log.info "auto refresh every 5 minutes" sendEvent(name: "auto_refresh", value: "5 minutes") runEvery5Minutes(refresh) } if (autoRefresh == 2) { log.info "auto refresh every 10 minutes" sendEvent(name: "auto_refresh", value: "10 minutes") runEvery10Minutes(refresh) } if (autoRefresh == 3) { log.info "auto refresh every 15 minutes" sendEvent(name: "auto_refresh", value: "15 minutes") runEvery15Minutes(refresh) } if (autoRefresh == 4) { if (txtEnable) log.info "auto refresh every 30 minutes" sendEvent(name: "auto_refresh", value: "30 minutes") runEvery30Minutes(refresh) } if (autoRefresh == 5) { if (txtEnable) log.info "auto refresh every 1 hour" sendEvent(name: "auto_refresh", value: "1 hour") runEvery1Hour(refresh) } if (autoRefresh == 6) { if (txtEnable) log.info "auto refresh every 3 hours" sendEvent(name: "auto_refresh", value: "3 hours") runEvery3Hours(refresh) } } def setSwitchDelay(value) { if (value == null) { sendEvent(name: "momentary_switch_delay", value: "2 sec", displayed: true) if (txtEnable) log.warn "incomplete parameter list supplied... momentay switch enabled set to default 20 (2 sec)" return delayBetween([ secureCmd(zwave.configurationV1.configurationSet(scaledConfigurationValue: 20, parameterNumber: 11, size: 1)), secureCmd(zwave.configurationV1.configurationGet(parameterNumber: 11)) ],1000) } if (value == 0) { sendEvent(name: "momentary_switch_delay", value: "disabled", displayed: true) if (txtEnable) log.info "set default 0 sec switch delay, momentay state disabled" return delayBetween([ secureCmd(zwave.configurationV1.configurationSet(scaledConfigurationValue: 0, parameterNumber: 11, size: 1)), secureCmd(zwave.configurationV1.configurationGet(parameterNumber: 11)) ],1000) } if (value == 1) { sendEvent(name: "momentary_switch_delay", value: "1 sec", displayed: true) if (txtEnable) log.info "momentay switch state enabled 1 sec" return delayBetween([ secureCmd(zwave.configurationV1.configurationSet(scaledConfigurationValue: 10, parameterNumber: 11, size: 1)), secureCmd(zwave.configurationV1.configurationGet(parameterNumber: 11)) ],1000) } if (value == 2) { sendEvent(name: "momentary_switch_delay", value: "2 sec", displayed: true) if (txtEnable) log.info "momentay switch state enabled 2 sec" return delayBetween([ secureCmd(zwave.configurationV1.configurationSet(scaledConfigurationValue: 20, parameterNumber: 11, size: 1)), secureCmd(zwave.configurationV1.configurationGet(parameterNumber: 11)) ],1000) } if (value == 3) { sendEvent(name: "momentary_switch_delay", value: "3 sec", displayed: true) if (txtEnable) log.info "momentay switch state enabled 3 sec" return delayBetween([ secureCmd(zwave.configurationV1.configurationSet(scaledConfigurationValue: 30, parameterNumber: 11, size: 1)), secureCmd(zwave.configurationV1.configurationGet(parameterNumber: 11)) ],1000) } } def parse(String description) { if (logEnable) log.debug "description is: ${description}" def result = null def cmd = zwave.parse(description, [0x72: 1, 0x86: 1, 0x71: 1, 0x30: 1, 0x31: 5, 0x35: 1, 0x70: 1, 0x85: 1, 0x25: 1, 0x03: 1, 0x20: 1, 0x84: 1]) if (logEnable) log.debug "command value is: $cmd" // Mimo sent a power loss report // if (cmd.CMD == "7105") { if (cmd == "7105") { log.debug "command value is: $cmd" log.warn "Device lost power" sendEvent(name: "powered", value: "powerOff", descriptionText: "$device.displayName lost power") } else { sendEvent(name: "powered", value: "powerOn", descriptionText: "$device.displayName regained power") } if (cmd) { result = zwaveEvent(cmd) if (result) { if (logEnable) log.debug "Parsed command: ${result} raw: ${description}" } else { if (logEnable) log.debug "Unhandled command: ${description}" } } else { if (logEnable) log.debug "Unparsed command: ${description}" } return result } def sensorValueEvent(Short value) { if (value) { sendEvent(name: "contact", value: "open") sendEvent(name:"lock", value:"unlocked") doorStateTimer() if (logEnable) runIn(1800,logsOff) } else { sendEvent(name: "contact", value: "closed") sendEvent(name: "door", value: "closed") sendEvent(name: "switch", value: "off") // doorStateTimer() } } def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) { [name: "switch", value: cmd.value ? "on" : "off", type: "physical"] } def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) { sensorValueEvent(cmd.value) } def zwaveEvent(hubitat.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) { sensorValueEvent(cmd.sensorValue) } //we caught this up in the parse method. This method not used. def zwaveEvent(hubitat.zwave.commands.alarmv1.AlarmReport cmd) { log.warn "We lost power" } // Handles all Z-Wave commands we aren't interested in def zwaveEvent(hubitat.zwave.Command cmd) { [:] } def lock() { if (device.currentValue("contact") == "closed") { if (txtEnable) log.info "locked door" sendEvent(name:"lock", value:"locked") } else { log.warn "not locked the door is open" } } def unlock() { if (txtEnable) log.info "unlocked door" sendEvent(name:"lock", value:"unlocked") } // used for manually opening and closing door to confirm door state & prevent continuously refresh loop def doorStateTimer() { if (txtEnable) log.info "door state timer" if (doortimer == null) { doortimer=30 } else { doortimer = Integer.parseInt(doortimer) } sendEvent(name: "door_timer", value: doortimer) runIn(doortimer, doorState) } // used to do a refresh after a open/close command to confirm door state def check_doorState() { if (txtEnable) log.info "running check door state" if (doortimer == null) { doortimer=30 } else { doortimer=Integer.parseInt(doortimer) } sendEvent(name: "door_timer", value: doortimer) if(doorCheckEnable) runIn(doortimer, refresh) runIn(doortimer+5, doorState) } def doorState() { if (txtEnable) log.info "getting door state" if (device.currentValue("contact") == "closed") { sendEvent(name: "door", value: "closed") sendEvent(name: "switch", value: "off") log.info "door state closed" autoClose() autoRefresh() // unschedule(close) } else { autoClose() autoRefresh() sendEvent(name: "door", value: "open") sendEvent(name:"lock", value:"unlocked") log.info "door state open" } } def on() { if (device.currentValue("lock") == "unlocked" & (device.currentValue("contact") == "closed" & device.currentValue("switch") == "off")) { log.info "Sending ON open cmd to open garage door" sendEvent(name: "door", value: "opening") sendEvent(name: "switch", value: "on") check_doorState() return delayBetween([ zwave.basicV1.basicSet(value: 0xFF).format(), zwave.basicV1.basicGet().format() ],1000) } if (device.currentValue("contact") == "open" & device.currentValue("door") != "closed" & device.currentValue("switch") == "off") { log.info "Sending ON close cmd to close garage door" sendEvent(name: "door", value: "closing") sendEvent(name: "switch", value: "on") check_doorState() return delayBetween([ zwave.basicV1.basicSet(value: 0xFF).format(), zwave.basicV1.basicGet().format() ],1000) } else { log.warn "no on cmd sent, door is locked or moving or switch is already on" check_doorState() } } def off() { log.info "Sending off cmd to door switch" sendEvent(name: "switch", value: "off") return delayBetween([ zwave.basicV1.basicSet(value: 0x00).format(), zwave.basicV1.basicGet().format() ],1000) } def open() { if (device.currentValue("lock") == "unlocked" & device.currentValue("contact") == "closed") { log.info "Sending OPEN cmd to open garage door" sendEvent(name: "door", value: "opening") sendEvent(name: "contact", value: "open") check_doorState() return delayBetween([ zwave.basicV1.basicSet(value: 0x00).format(), zwave.basicV1.basicSet(value: 0xFF).format(), zwave.basicV1.basicGet().format() ],1000) } else { log.warn "no open cmd sent door is already open or locked" check_doorState() } } def close() { if (device.currentValue("contact") == "open" & device.currentValue("door") == "open") { log.info "Sending CLOSE cmd to close garage door" sendEvent(name: "door", value: "closing") check_doorState() return delayBetween([ zwave.basicV1.basicSet(value: 0x00).format(), zwave.basicV1.basicSet(value: 0xFF).format(), zwave.basicV1.basicGet().format() ],1000) } else { log.warn "No close cmd sent door is already closed or in transit" check_doorState() } } def push() { if (device.currentValue("lock") == "unlocked" & device.currentValue("contact") == "closed") { log.info "Sending PUSH open event to open garage door" sendEvent(name: "door", value: "opening") check_doorState() return delayBetween([ zwave.basicV1.basicSet(value: 0x00).format(), zwave.basicV1.basicSet(value: 0xFF).format(), zwave.basicV1.basicGet().format() ],1000) } if (device.currentValue("contact") == "open" & device.currentValue("door") == "open") { log.info "Sending PUSH close event to close garage door" sendEvent(name: "door", value: "closing") check_doorState() return delayBetween([ zwave.basicV1.basicSet(value: 0x00).format(), zwave.basicV1.basicSet(value: 0xFF).format(), zwave.basicV1.basicGet().format() ],1000) } else { log.warn "no push cmd sent, door is locked or moving" check_doorState() } } def poll() { refresh() } // PING is used by Device-Watch in attempt to reach the Device def ping() { refresh() } def refresh() { log.info "Refreshing" if (logEnable) runIn(1800,logsOff) // doorState() autoRefresh() return delayBetween([ // get switch state secureCmd(zwave.switchBinaryV1.switchBinaryGet()), // get sig1 door magnetic dry contact state secureCmd(zwave.sensorBinaryV1.sensorBinaryGet()), // get switch state secureCmd(zwave.basicV1.basicGet()), // get alert thresholds setting secureCmd(zwave.configurationV1.configurationGet(parameterNumber: 8)), // get alarms setting secureCmd(zwave.configurationV1.configurationGet(parameterNumber: 9)), // get momentary switch setting secureCmd(zwave.configurationV1.configurationGet(parameterNumber: 11)) ],1000) } def logsOff(){ log.warn "debug logging disabled..." device.updateSetting("logEnable",[value:"false",type:"bool"]) } def configure() { log.info "Configuring defaults" log.info "momentay switch state enabled 2 sec" if (logEnable) runIn(1800,logsOff) sendEvent(name: "momentary_switch_delay", value: "2 sec") sendEvent(name: "switch", value: "off") sendEvent(name:"lock", value:"unlocked") check_doorState() autoClose() autoRefresh() return delayBetween([ // enable analog alert thresholds for door contacts 0 disables secureCmd(zwave.configurationV1.configurationSet(scaledConfigurationValue: 3, parameterNumber: 8, size: 1)), // tell device to send multivalue sensor updates every 1 = 10 seconds secureCmd(zwave.configurationV1.configurationSet(scaledConfigurationValue: 3, parameterNumber: 9, size: 1)), // sets momentary switch set 0-30 0 (disabled) 10 = 1 seconds configured default for 20 (2 sec) secureCmd(zwave.configurationV1.configurationSet(scaledConfigurationValue: 20, parameterNumber: 11, size: 1)), // enable alarms to be sent to hub secureCmd(zwave.associationV1.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId])), // enable multivalue sensor updates to be sent to hub secureCmd(zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId])), // sets switch to off zwave.basicV1.basicSet(value: 0x00).format(), zwave.basicV1.basicGet().format() ],1000) } def getSwitchDelay() { def cmds = [] cmds = [secureCmd(zwave.configurationV1.configurationGet(parameterNumber: 11))] } def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) { if (txtEnable) log.info "ConfigurationReport- parameterNumber:${cmd.parameterNumber}, size:${cmd.size}, value:${cmd.scaledConfigurationValue}" } private secureCmd(cmd) { if (getDataValue("zwaveSecurePairingComplete") == "true") { return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() } else { return cmd.format() } }