/** * Dome Door Sensor v1.1.6 * (Model: DMWD1) * * Author: * Kevin LaFramboise (krlaframboise) * * URL to documentation: https://community.smartthings.com/t/release-dome-door-sensor-official/76321 * * * Changelog: * * 1.1.6 (08/15/2018) * - Added support for new mobile app. * * 1.1.5 (12/25/2017) * - Implemented ST Color Scheme * * 1.1.4 (04/20/2017) * - Added workaround for ST Health Check bug. * * 1.1.3 (03/12/2017) * - Adjusted health check to allow it to skip a checkin before going offline. * * 1.1.1 (03/07/2017) * - Added comments and health check capability. * * 1.0 (02/01/2017) * - Initial Release * * * 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. * */ metadata { definition ( name: "Dome Door Sensor", namespace: "krlaframboise", author: "Kevin LaFramboise", vid: "generic-contact" ) { capability "Sensor" capability "Contact Sensor" capability "Battery" capability "Configuration" capability "Refresh" capability "Health Check" attribute "lastCheckin", "string" fingerprint deviceId: "0x0701", inClusters: "0x30, 0x59, 0x5A, 0x5E, 0x70, 0x71, 0x72, 0x73, 0x80, 0x84, 0x85, 0x86" fingerprint mfr:"021F", prod:"0003", model:"0101" fingerprint mfr:"0258", prod:"0003", model:"0082" } simulator { } preferences { input "wakeUpInterval", "enum", title: "Checkin Interval:", defaultValue: checkinIntervalSetting, required: false, displayDuringSetup: true, options: checkinIntervalOptions.collect { it.name } input "batteryReportingInterval", "enum", title: "Battery Reporting Interval:", defaultValue: batteryReportingIntervalSetting, required: false, displayDuringSetup: true, options: checkinIntervalOptions.collect { it.name } input "debugOutput", "bool", title: "Enable debug logging?", defaultValue: true, required: false } tiles(scale: 2) { multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){ tileAttribute ("device.contact", key: "PRIMARY_CONTROL") { attributeState("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#e86d13") attributeState("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#00a0dc") } } standardTile("refresh", "device.refresh", width: 2, height: 2) { state "refresh", label:'Refresh', action: "refresh", icon:"st.secondary.refresh-icon" } valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2){ state "battery", label:'${currentValue}% battery', unit:"" } main "contact" details(["contact", "refresh", "battery"]) } } // Sets flag so that configuration is updated the next time it wakes up. def updated() { // This method always gets called twice when preferences are saved. if (!isDuplicateCommand(state.lastUpdated, 3000)) { state.lastUpdated = new Date().time logTrace "updated()" logForceWakeupMessage "The configuration will be updated the next time the device wakes up." state.pendingChanges = true } } // Initializes the device state when paired and updates the device's configuration. def configure() { logTrace "configure()" def cmds = [] if (!state.isConfigured) { state.isConfigured = true state.pendingChanges = false state.pendingRefresh = false logTrace "Waiting 1 second because this is the first time being configured" cmds << "delay 1000" cmds << sensorBinaryGetCmd() cmds << batteryGetCmd() } cmds << wakeUpIntervalSetCmd(checkinIntervalSettingMinutes) initializeCheckin() logDebug "Sending configuration to device." return delayBetween(cmds, 1000) } private initializeCheckin() { // Set the Health Check interval so that it can be skipped once plus 2 minutes. def checkInterval = ((checkinIntervalSettingMinutes * 2 * 60) + (2 * 60)) sendEvent(name: "checkInterval", value: checkInterval, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) } // Required for HealthCheck Capability, but doesn't actually do anything because this device sleeps. def ping() { logDebug "ping()" } // Forces the configuration to be resent to the device the next time it wakes up. def refresh() { logForceWakeupMessage "The sensor data will be refreshed the next time the device wakes up." state.pendingRefresh = true } private logForceWakeupMessage(msg) { logDebug "${msg} You can force the device to wake up immediately by pressing the connect button once." } // Processes messages received from device. def parse(String description) { def result = [] sendEvent(name: "lastCheckin", value: convertToLocalTimeString(new Date()), displayed: false, isStateChange: true) def cmd = zwave.parse(description, commandClassVersions) if (cmd) { result += zwaveEvent(cmd) } else { logDebug "Unable to parse description: $description" } return result } private getCommandClassVersions() { [ 0x30: 2, // Sensor Binary 0x59: 1, // AssociationGrpInfo 0x5A: 1, // DeviceResetLocally 0x5E: 2, // ZwaveplusInfo 0x70: 1, // Configuration 0x71: 3, // Alarm v1 or Notification v4 0x72: 2, // ManufacturerSpecific 0x73: 1, // Powerlevel 0x80: 1, // Battery 0x84: 2, // WakeUp 0x85: 2, // Association 0x86: 1 // Version (2) ] } // Updates devices configuration, if needed, and creates the event with the last lastcheckin event. def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) { logTrace "WakeUpNotification: $cmd" def cmds = [] if (!state.isConfigured || state.pendingChanges) { state.pendingChanges = false cmds += configure() } if (state.pendingRefresh || canReportBattery()) { state.pendingRefresh = false cmds << batteryGetCmd() } else { logTrace "Skipping battery check because it was already checked within the last ${batteryReportingIntervalSetting}." } if (cmds) { cmds << "delay 2000" } cmds << wakeUpNoMoreInfoCmd() return response(delayBetween(cmds, 250)) } private canReportBattery() { def reportEveryMS = (batteryReportingIntervalSettingMinutes * 60 * 1000) return (!state.lastBatteryReport || ((new Date().time) - state.lastBatteryReport > reportEveryMS)) } // Creates the event for the battery level. def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { logTrace "BatteryReport: $cmd" def val = (cmd.batteryLevel == 0xFF ? 1 : cmd.batteryLevel) if (val > 100) { val = 100 } state.lastBatteryReport = new Date().time [ createEvent(getEventMap("battery", val, "%")) ] } // Contact event is being created using Sensor Binarry command class so this event is ignored. def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) { logTrace "NotificationReport: $cmd" return [] } // Creates the contact open/closed event. def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) { logTrace "SensorBinaryReport: $cmd" def val = (cmd.sensorValue == 0xFF ? "open" : "closed") def result = [] result << createEvent(getEventMap("contact", val)) return result } // Logs unexpected events from the device. def zwaveEvent(physicalgraph.zwave.Command cmd) { logDebug "Unhandled Command: $cmd" return [] } private getEventMap(name, value, unit=null) { def isStateChange = (device.currentValue(name) != value) def eventMap = [ name: name, value: value, displayed: isStateChange, isStateChange: isStateChange, descriptionText: "${name} ${value}${unit ? unit : ''}" ] if (unit) { eventMap.unit = unit } if (isStateChange) { logDebug "${eventMap.descriptionText}" } logTrace "Creating Event: ${eventMap}" return eventMap } private wakeUpIntervalSetCmd(minutesVal) { state.checkinIntervalMinutes = minutesVal logTrace "wakeUpIntervalSetCmd(${minutesVal})" return zwave.wakeUpV2.wakeUpIntervalSet(seconds:(minutesVal * 60), nodeid:zwaveHubNodeId).format() } private wakeUpNoMoreInfoCmd() { return zwave.wakeUpV2.wakeUpNoMoreInformation().format() } private batteryGetCmd() { return zwave.batteryV1.batteryGet().format() } private sensorBinaryGetCmd() { return zwave.sensorBinaryV2.sensorBinaryGet().format() } // Settings private getCheckinIntervalSettingMinutes() { return convertOptionSettingToInt(checkinIntervalOptions, checkinIntervalSetting) ?: 720 } private getCheckinIntervalSetting() { return settings?.wakeUpInterval ?: findDefaultOptionName(checkinIntervalOptions) } private getBatteryReportingIntervalSettingMinutes() { return convertOptionSettingToInt(checkinIntervalOptions, batteryReportingIntervalSetting) ?: checkinIntervalSettingMinutes } private getBatteryReportingIntervalSetting() { return settings?.batteryReportingInterval ?: findDefaultOptionName(checkinIntervalOptions) } private getCheckinIntervalOptions() { [ [name: "10 Minutes", value: 10], [name: "15 Minutes", value: 15], [name: "30 Minutes", value: 30], [name: "1 Hour", value: 60], [name: "2 Hours", value: 120], [name: "3 Hours", value: 180], [name: "6 Hours", value: 360], [name: "9 Hours", value: 540], [name: formatDefaultOptionName("12 Hours"), value: 720], [name: "18 Hours", value: 1080], [name: "24 Hours", value: 1440] ] } private convertOptionSettingToInt(options, settingVal) { return safeToInt(options?.find { "${settingVal}" == it.name }?.value, 0) } private formatDefaultOptionName(val) { return "${val}${defaultOptionSuffix}" } private findDefaultOptionName(options) { def option = options?.find { it.name?.contains("${defaultOptionSuffix}") } return option?.name ?: "" } private getDefaultOptionSuffix() { return " (Default)" } private safeToInt(val, defaultVal=-1) { return "${val}"?.isInteger() ? "${val}".toInteger() : defaultVal } private convertToLocalTimeString(dt) { def timeZoneId = location?.timeZone?.ID if (timeZoneId) { return dt.format("MM/dd/yyyy hh:mm:ss a", TimeZone.getTimeZone(timeZoneId)) } else { return "$dt" } } private isDuplicateCommand(lastExecuted, allowedMil) { !lastExecuted ? false : (lastExecuted + allowedMil > new Date().time) } private logDebug(msg) { if (settings?.debugOutput || settings?.debugOutput == null) { log.debug "$msg" } } private logTrace(msg) { // log.trace "$msg" }