diff --git a/AeotecHDSS.groovy b/AeotecHDSS.groovy deleted file mode 100644 index ffca729..0000000 --- a/AeotecHDSS.groovy +++ /dev/null @@ -1,935 +0,0 @@ -/** - * - * Aeotec Heavy Duty Smart Switch - * - * Copyright 2019 peng1can - * based on jbisson, James P ('elasticdev'), Mr Lucky, lg kahn code. - * - * - * * The energy cost (current) represent the amount of money you'll pay if the current load stay the same all the time - * vs - * The energy cost (cumulative) is the amount of money you'll pay based on the amount of energy used between now and - * the last time you did a reset. You can clear this counter by hitting the reset icon. - * This will take into account the variable aspect of your energy consumption. - * - * * - * If you are pairing in secure inclusive mode, make sure you turn it on in the config page. - * - * - * Revision History - * ============================================== - * 2019-05-13 Version 5.4.0 Modified for Heavy Duty Smart Switch on Hubitat - * 2019-03-25 Version 5.3.0 Fixed watt display and other small display tweaks - * 2018-02-10 Version 5.2.1 Small Crash protection fix (reported by: dkorunic) - * 2017-09-04 Version 5.2.0 Removed defaultValues on preference screen as a work-around for a platform bug, crash protection fixes - * 2017-09-02 Version 5.1.6 More display clean-up and fixed reversed ranges on min watts/percent change in report prefs (Nezmo) - * 2017-08-15 Version 5.1.5 Cleaned-up display for iOS and other display issues (Nezmo) - * 2017-02-24 Version 5.1.4 Bug fixed around getDeviceInfo - * 2017-01-21 Version 5.1.0 Added energy meter cost per hours/week/month/year feature, fixed display issues - * 2016-11-13 Version 5.0.0 Added Z-Wave secure inclusion support (note that you'll need to manually set it up during configuration) - * 2016-11-12 Version 4.0.5 Added AT&T rebrand fingerprint + added force refresh report notification update preference - * 2016-08-31 Version 4.0.4 Fixed fingerprint number. - * 2016-08-15 Version 4.0.3 Fixed setcolor logic when using the Color Control capability - * 2016-08-12 Version 4.0.2 Added version in preference setting - * 2016-08-11 Version 4.0.1 Added switch disabled visual on the main tile, added firmware version - * 2016-08-11 Version 4.0.0 Added log preference, enable/disable switch preference, added dev documentation, changed fingerprint - * 2016-08-08 Version 3.0.1 Adapt device handler to support gen5 version 3.1 - * 2016-08-08 Version 3.0 - * 2015-09-01 version 2 - lg kahn - * - * - * Developer's Notes - * Raw Description 0 0 0x1001 0 0 0 11 0x5E 0x25 0x26 0x33 0x70 0x27 0x32 0x81 0x85 0x59 0x72 0x86 0x7A 0x73 0xEF 0x5A 0x82 - * Z-Wave Supported Command Classes: - * Code Name Version - * ==== ====================================== ======= - * 0x5E COMMAND_CLASS_ZWAVE_PLUS_INFO - * 0x25 COMMAND_CLASS_SWITCH_BINARY V1 Implemented - * 0x26 COMMAND_CLASS_SWITCH_MULTILEVEL V3 (new in gen5) Implemented - not used - * 0x32 COMMAND_CLASS_METER V3 Implemented - * 0x33 COMMAND_CLASS_COLOR V1 (new in gen5) Not implemented - * 0x70 COMMAND_CLASS_CONFIGURATION V1 Implemented - * 0x27 COMMAND_CLASS_SWITCH_ALL V1 Not implemented - * 0x81 COMMAND_CLASS_CLOCK V1 (new in gen5) Not implemented - * 0x85 COMMAND_CLASS_ASSOCIATION V2 Not implemented - * 0x72 COMMAND_CLASS_MANUFACTURER_SPECIFIC V2 Implemented - * 0x59 COMMAND_CLASS_ASSOCIATION_GRP_INFO V1 Not implemented - * 0x86 COMMAND_CLASS_VERSION V2 Implemented - * 0xEF COMMAND_CLASS_MARK V1 Not implemented - * 0x82 COMMAND_CLASS_HAIL V1 Implemented - not used - * 0x7A COMMAND_CLASS_FIRMWARE_UPDATE_MD_V2 V2 (new in gen5) Implemented - * 0x73 COMMAND_CLASS_POWERLEVEL V1 (new in gen5) Not implemented - * 0x5A COMMAND_CLASS_DEVICE_RESET_LOCALLY V1 (new in gen5) Not implemented - * 0x98 COMMAND_CLASS_SECURITY V1 (For secure Inclusion) Implemented - * - */ - -def clientVersion() { - return "5.4.0" -} - -metadata { - definition(name: "Aeotec Heavy Duty Smart Switch", namespace: "peng1can", author: "D Canfield") { - capability "Switch" - capability "Polling" - capability "Power Meter" - capability "Energy Meter" - capability "Refresh" -//// capability "Switch Level" - capability "Sensor" - capability "Actuator" - capability "Configuration" -//// capability "Color Control" - - command "energy" -//// command "momentary" -//// command "nightLight" - - command "reset" - command "factoryReset" -//// command "setBrightnessLevel" - command "getDeviceInfo" - - attribute "deviceMode", "String" - - // Base on https://community.smartthings.com/t/new-z-wave-fingerprint-format/48204 - fingerprint mfr: "0134", prod: "0259", model: "0078" - } - - tiles(scale: 2) { - multiAttributeTile(name: "mainPanel", type: "lighting", width: 6, height: 4, canChangeIcon: true) { - tileAttribute("device.switch", key: "PRIMARY_CONTROL") { - attributeState "on", label: '${name}', action: "switch.off", icon: "st.Appliances.appliances17", backgroundColor: "#00a0dc", nextState: "turningOff" - attributeState "off", label: '${name}', action: "switch.on", icon: "st.Appliances.appliances17", backgroundColor: "#ffffff", nextState: "turningOn" - attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.Appliances.appliances17", backgroundColor: "#00a0dc", nextState: "turningOff" - attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.Appliances.appliances17", backgroundColor: "#ffffff", nextState: "turningOn" - } -// tileAttribute("statusText3", key: "SECONDARY_CONTROL") { -// attributeState "statusText3", label: '${currentValue}' -// } - } - standardTile("deviceMode", "deviceMode", canChangeIcon: true, canChangeBackground: false, width: 2, height: 2) { - state "energy", label: 'energy', action: "momentary", icon: "http://mail.lgk.com/aeonv6orange.png" - state "momentary", label: 'momentary', action: "nightLight", icon: "http://mail.lgk.com/aeonv6white.png" - state "nightLight", label: 'NightLight', action: "energy", icon: "http://mail.lgk.com/aeonv6blue.png" - } - - valueTile("power", "device.power", width: 2, height: 1, decoration: "flat") { - state "default", label: '${currentValue} W' - } - - valueTile("energy", "device.energy", width: 2, height: 1, decoration: "flat") { - state "default", label: '${currentValue} kWh' - } - - valueTile("amperage", "device.amperage", width: 2, height: 1, decoration: "flat") { - state "default", label: '${currentValue} A' - } - - valueTile("voltage", "device.voltage", width: 4, height: 1, decoration: "flat") { - state "default", label: '${currentValue} v' - } - valueTile("energyMeterRuntime", "energyMeterRuntime", width: 2, height: 1, decoration: "flat") { - state "default", label: 'Energy Meter Running Since:' - } - valueTile("currentEnergyCostTxt", "currentEnergyCostTxt", width: 2, height: 1, decoration: "flat") { - state "default", label: 'Energy Cost (Current):' - } - - valueTile("currentEnergyCostHour", "currentEnergyCostHour", width: 1, height: 1, decoration: "flat") { - state "default", label: 'Per\nHour\n$${currentValue}' - } - - valueTile("currentEnergyCostWeek", "currentEnergyCostWeek", width: 1, height: 1, decoration: "flat") { - state "default", label: 'Per\nWeek\n$${currentValue}' - } - - valueTile("currentEnergyCostMonth", "currentEnergyCostMonth", width: 1, height: 1, decoration: "flat") { - state "default", label: 'Per\nMonth\n$${currentValue}' - } - - valueTile("currentEnergyCostYear", "currentEnergyCostYear", width: 1, height: 1, decoration: "flat") { - state "default", label: 'Per\nYear\n$${currentValue}' - } - - valueTile("cumulativeEnergyCostTxt", "cumulativeEnergyCostTxt", width: 2, height: 1, decoration: "flat") { - state "default", label: 'Energy Cost (Cumulative)\nSince ${currentValue}:' - } - - valueTile("cumulativeEnergyCostHour", "cumulativeEnergyCostHour", width: 1, height: 1, decoration: "flat") { - state "default", label: 'Per\nHour \n$${currentValue}' - } - - valueTile("cumulativeEnergyCostWeek", "cumulativeEnergyCostWeek", width: 1, height: 1, decoration: "flat") { - state "default", label: 'Per\nWeek\n$${currentValue}' - } - - valueTile("cumulativeEnergyCostMonth", "cumulativeEnergyCostMonth", width: 1, height: 1, decoration: "flat") { - state "default", label: 'Per\nMonth\n$${currentValue}' - } - - valueTile("cumulativeEnergyCostYear", "cumulativeEnergyCostYear", width: 1, height: 1, decoration: "flat") { - state "default", label: 'Per\nYear \n$${currentValue}' - } - - controlTile("levelSliderControl", "device.brightnessLevel", "slider", width: 2, height: 1) { - state "level", action: "switch level.setLevel" - } - - valueTile("levelSliderTxt", "device.brightnessLevel", decoration: "flat") { - state "brightnessLevel", label: '${currentValue} %' - } - - standardTile("refresh", "device.switch", decoration: "flat", width: 2, height: 2) { - state "default", label: "", action: "refresh.refresh", icon: "st.secondary.refresh" - } - - standardTile("reset", "device.energy", decoration: "flat", width: 2, height: 2) { - state "default", label: 'reset', action: "reset", icon: "st.secondary.refresh-icon" - } - - controlTile("rgbSelector", "device.color", "color", height: 3, width: 2) { - state "color", action: "setColor" - } - - standardTile("configure", "device.power", inactiveLabel: false, decoration: "flat", width: 1, height: 1) { - state "configure", label: '', action: "configuration.configure", icon: "st.secondary.configure" - } - - valueTile("deviceInfo", "deviceInfo", decoration: "flat", width: 6, height: 2) { - state "default", label: '${currentValue}', action: "getDeviceInfo" - } - - main(["mainPanel", "power", "voltage", "amperage"]) - details(["mainPanel", "deviceMode", "power", "amperage", "voltage", - "currentEnergyCostTxt", "currentEnergyCostHour", "currentEnergyCostWeek", "currentEnergyCostMonth", "currentEnergyCostYear", - "cumulativeEnergyCostTxt", "cumulativeEnergyCostHour", "cumulativeEnergyCostWeek", "cumulativeEnergyCostMonth", "cumulativeEnergyCostYear", - "configure", "refresh", "reset", "deviceInfo"]) - } -} - -preferences { -//// input title: "", description: "Aeon Smart Switch 6 (gen5) v${clientVersion()}", displayDuringSetup: true, type: "paragraph", element: "paragraph" - input name: "overloadProtection", type: "bool", title: "Overload Protection", defaultValue: true, displayDuringSetup: true -//// input name: "ledAfterStatusPowerOn", type: "enum", title: "LED Status After PowerOn", options: ["Last Status", "Always On", "Always Off"], defaultValue: "Last Status" - input name: "switchDisabled", type: "bool", title: "Disable switch on/off\n", defaultValue: "false", displayDuringSetup: true, required: true - input name: "refreshInterval", type: "number", title: "Refresh interval \n\nSet the refresh time interval (seconds) between each report [Default (300)].\n", displayDuringSetup: true, required: true - input name: "switchAll", type: "enum", title: "Respond to switch all?\n", description: "How does switch respond to the 'Switch All' command", options: ["Disabled", "Off Enabled", "On Enabled", "On and Off Enabled"], defaultValue: "On and Off Enabled", displayDuringSetup: true, required: false - input name: "forceStateChangeOnReport", type: "bool", title: "Force state change when receiving a report ? If true, you'll always get notification even if report data doesn't change.\n", defaultValue: "false", displayDuringSetup: true, required: true - input name: "secureInclusionOverride", type: "bool", title: "Is this device in secure inclusive mode?\n", defaultValue: "false", displayDuringSetup: true, required: true - - input name: "onlySendReportIfValueChange", type: "bool", title: "Only send report if value change (either in terms of wattage or a %)\n", defaultValue: "false", displayDuringSetup: true, required: true -//// input title: "", description: "The next two parameters are only functional if the 'only send report' is set to true.", type: "paragraph", element: "paragraph", displayDuringSetup: true, required: true - - input name: "minimumChangeWatts", type: "number", title: "Minimum change in wattage for a report to be sent (0 - 60000) [Default (25)].\n", range: "0..60000", displayDuringSetup: true, required: true - input name: "minimumChangePercent", type: "number", title: "Minimum change in percentage for a report to be sent (0 - 100) [Default (5)]\n", range: "0..100", displayDuringSetup: true, required: true - - input name: "costPerKwh", type: "decimal", title: "Cost per kWh (Used for energy cost /per kWh) [Default (0.12)]\n", displayDuringSetup: true, required: true - - input name: "includeWattInReport", type: "bool", title: "Include energy meter (W) in report?\n", defaultValue: "true", displayDuringSetup: true, required: true - input name: "includeVoltageInReport", type: "bool", title: "Include voltage (V) in report?\n", defaultValue: "true", displayDuringSetup: true, required: true - input name: "includeCurrentInReport", type: "bool", title: "Include current (A) in report?\n", defaultValue: "true", displayDuringSetup: true, required: true - input name: "includeCurrentUsageInReport", type: "bool", title: "Include current usage (kWh) in report?\n", defaultValue: "true", displayDuringSetup: true, required: true - -//// input title: "", description: "Logging", type: "paragraph", element: "paragraph" - input name: "isLogLevelTrace", type: "bool", title: "Show trace log level ?\n", defaultValue: "false", displayDuringSetup: true, required: true - input name: "isLogLevelDebug", type: "bool", title: "Show debug log level ?\n", defaultValue: "true", displayDuringSetup: true, required: true -} - -/******************************************************************************* - * Z-WAVE PARSE / EVENTS * - ******************************************************************************/ - -/** - * parse - Called when messages from a device are received from the hub - * - * The parse method is responsible for interpreting those messages and returning Event definitions. - * - * String description The message from the device - */ -def parse(String description) { - def result = null - logTrace "parse: '$description'" - - if (description != "updated") { - if (description.contains("command: 5E02")) { - logInfo "Ignoring command 5E02 has it's not supported by the platform." - return - } - - def cmd = zwave.parse(description, [0x98: 1, 0x20: 1, 0x26: 3, 0x70: 1, 0x32: 3]) - logTrace "cmd: '$cmd'" - - if (cmd) { - result = zwaveEvent(cmd) - //log.debug("'$description' parsed to $result $result?.name") - } else { - logError "Couldn't zwave.parse '$description'" - } - } - - updateStatus() - result -} - -/** - * COMMAND_CLASS_SECURITY (0x98) - * - * - */ -def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { - def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x26: 3, 0x70: 1, 0x32: 3]) - logTrace "secure cmd: '$cmd'" - state.deviceInfo['secureInclusion'] = true; - - // can specify command class versions here like in zwave.parse - if (encapsulatedCommand) { - return zwaveEvent(encapsulatedCommand) - } else { - logError "Unable to extract encapsulated cmd from $cmd" - } -} - -/** - * COMMAND_CLASS_SECURITY (0x98) - * - * - */ -def zwaveEvent(hubitat.zwave.commands.securityv1.NetworkKeyVerify cmd) { - log.debug "NetworkKeyVerify with cmd: $cmd (node is securely included)" - - //after device securely joined the network, call configure() to config device - state.deviceInfo['secureInclusion'] = true; - updateDeviceInfo() -} - -/** - * COMMAND_CLASS_SWITCH_BINARY (0x25) - * - * Short value 0xFF for on, 0x00 for off - */ -def zwaveEvent(hubitat.zwave.commands.switchbinaryv1.SwitchBinarySet cmd) { - createEvent(name: "switch", value: cmd.switchValue ? "on" : "off") - - //return createEvent(name: "switch", value: cmd.value ? "on" : "off") -} - -/** - * COMMAND_CLASS_SWITCH_BINARY (0x25) - * - * Short value 0xFF for on, 0x00 for off - */ -def zwaveEvent(hubitat.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { - createEvent(name: "switch", value: cmd.value ? "on" : "off", displayed: false, isStateChange: true) -} - -def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) { - -} - -/** - * COMMAND_CLASS_BASIC (0x20) - * This command is being ignored in secure inclusion mode. - * - * Short value 0xFF for on, 0x00 for off - */ -def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) { - return createEvent(name: "switch", value: cmd.value ? "on" : "off", displayed: false) -} - -/** - * COMMAND_CLASS_BASIC (0x20) - * - * This command is being ignored in secure inclusion mode. - * Short value 0xFF for on, 0x00 for off - */ -def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) { - return createEvent(name: "switch", value: cmd.value ? "on" : "off") -} - -/** - * COMMAND_CLASS_SWITCH_MULTILEVEL (0x26) - * - * Short value - */ -def zwaveEvent(hubitat.zwave.commands.switchmultilevelv3.SwitchMultilevelReport cmd) { - -} - -/** - * COMMAND_CLASS_METER (0x32) - * - * Integer deltaTime Time in seconds since last report - * Short meterType Unknown = 0, Electric = 1, Gas = 2, Water = 3 - * List meterValue Meter value as an array of bytes - * Double scaledMeterValue Meter value as a double - * List previousMeterValue Previous meter value as an array of bytes - * Double scaledPreviousMeterValue Previous meter value as a double - * Short size The size of the array for the meterValue and previousMeterValue - * Short scale The scale of the values: "kWh"=0, "kVAh"=1, "Watts"=2, "pulses"=3, "Volts"=4, "Amps"=5, "Power Factor"=6, "Unknown"=7 - * Short precision The decimal precision of the values - * Short rateType ??? - * Boolean scale2 ??? - */ -def zwaveEvent(hubitat.zwave.commands.meterv3.MeterReport cmd) { - if (cmd.meterType == 1) { - def eventList = [] - - if ("$costPerKwh" == "null") { - logError "costPerKwh is null, please go through the configuration page first" - return - } - - if (cmd.scale == 0) { - logDebug " got kwh $cmd.scaledMeterValue" - - BigDecimal costDecimal = ( costPerKwh as BigDecimal ) - def batteryRunTimeHours = getBatteryRuntimeInHours() - - eventList.push(internalCreateEvent([name: "cumulativeEnergyCostTxt", value: getBatteryRuntime() + "\n" + cmd.scaledMeterValue + " kWh"])); - eventList.push(internalCreateEvent([name: "cumulativeEnergyCostHour", value: String.format("%5.2f", cmd.scaledMeterValue / batteryRunTimeHours * costDecimal)])); - eventList.push(internalCreateEvent([name: "cumulativeEnergyCostWeek", value: String.format("%5.2f", cmd.scaledMeterValue / batteryRunTimeHours * costDecimal * 24 * 7)])); - eventList.push(internalCreateEvent([name: "cumulativeEnergyCostMonth", value: String.format("%5.2f", cmd.scaledMeterValue / batteryRunTimeHours * costDecimal * 24 * 30.42)])); - eventList.push(internalCreateEvent([name: "cumulativeEnergyCostYear", value: String.format("%5.2f", cmd.scaledMeterValue / batteryRunTimeHours * costDecimal * 24 * 365)])); - } else if (cmd.scale == 1) { - logDebug " got kVAh $cmd.scaledMeterValue" - eventList.push(internalCreateEvent([name: "energy", value: cmd.scaledMeterValue, unit: "kVAh"])); - } else if (cmd.scale == 2) { - logDebug " got wattage $cmd.scaledMeterValue" - eventList.push(internalCreateEvent([name: "power", value: Math.round(cmd.scaledMeterValue), unit: "W"])); - BigDecimal costDecimal = ( costPerKwh as BigDecimal ) - eventList.push(internalCreateEvent([name: "currentEnergyCostHour", value: String.format("%5.2f", (cmd.scaledMeterValue / 1000) * costDecimal)])); - eventList.push(internalCreateEvent([name: "currentEnergyCostWeek", value: String.format("%5.2f", (cmd.scaledMeterValue / 1000) * 24 * 7 * costDecimal)])); - eventList.push(internalCreateEvent([name: "currentEnergyCostMonth", value: String.format("%5.2f", (cmd.scaledMeterValue / 1000) * 24 * 30.42 * costDecimal)])); - eventList.push(internalCreateEvent([name: "currentEnergyCostYear", value: String.format("%5.2f", (cmd.scaledMeterValue / 1000) * 24 * 365 * costDecimal)])); - } else if (cmd.scale == 4) { // Volts - logDebug " got voltage $cmd.scaledMeterValue" - eventList.push(internalCreateEvent([name: "voltage", value: Math.round(cmd.scaledMeterValue), unit: "V"])); - } else if (cmd.scale == 5) { //amps scale 5 is amps even though not documented - logDebug " got amperage = $cmd.scaledMeterValue" - eventList.push(internalCreateEvent([name: "amperage", value: cmd.scaledMeterValue, unit: "A"])); - } else { - eventList.push(internalCreateEvent([name: "electric", value: cmd.scaledMeterValue, unit: ["pulses", "V", "A", "R/Z", ""][cmd.scale - 3]])); - } - - return eventList - - } -} - -/** - * COMMAND_CLASS_CONFIGURATION (0x70) - * - * List configurationValue - * Short parameterNumber - * Short size - */ -/* -def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) { - logTrace "received ConfigurationReport for " + cmd.parameterNumber + " (hex:" + Integer.toHexString(cmd.parameterNumber) + ") cmd: " + cmd - switch (cmd.parameterNumber) { - case 0x51: - logTrace "received device mode event" - if (cmd.configurationValue[0] == 0) { - return createEvent(name: "deviceMode", value: "energy", displayed: true) - } else if (cmd.configurationValue[0] == 1) { - return createEvent(name: "deviceMode", value: "momentary", displayed: true) - } else if (cmd.configurationValue[0] == 2) { - return createEvent(name: "deviceMode", value: "nightLight", displayed: true) - } - break; - case 0x54: - logTrace "received brightness level event" - return createEvent(name: "level", value: cmd.configurationValue[0], displayed: true) - break; - } -}*/ - -/** - * COMMAND_CLASS_HAIL (0x82) - * - */ -def zwaveEvent(hubitat.zwave.commands.hailv1.Hail cmd) { - logDebug "Switch button was pressed" - return createEvent(name: "hail", value: "hail", descriptionText: "Switch button was pressed") -} - -/** - * COMMAND_CLASS_VERSION (0x86) - * - * Short applicationSubVersion - * Short applicationVersion - * Short zWaveLibraryType - * Short zWaveProtocolSubVersion - * Short zWaveProtocolVersion - */ -def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) { - state.deviceInfo['applicationVersion'] = "${cmd.applicationVersion}" - state.deviceInfo['applicationSubVersion'] = "${cmd.applicationSubVersion}" - state.deviceInfo['zWaveLibraryType'] = "${cmd.zWaveLibraryType}" - state.deviceInfo['zWaveProtocolVersion'] = "${cmd.zWaveProtocolVersion}" - state.deviceInfo['zWaveProtocolSubVersion'] = "${cmd.zWaveProtocolSubVersion}" - - return updateDeviceInfo() -} - -/** - * COMMAND_CLASS_MANUFACTURER_SPECIFIC (0x72) - * - * Integer manufacturerId - * Integer productId - * Integer productTypeId - * - */ -def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { - state.deviceInfo['manufacturerId'] = "${cmd.manufacturerId}" - state.deviceInfo['manufacturerName'] = "${cmd.manufacturerName}" - state.deviceInfo['productId'] = "${cmd.productId}" - state.deviceInfo['productTypeId'] = "${cmd.productTypeId}" - - return updateDeviceInfo() -} - -/** - * COMMAND_CLASS_MANUFACTURER_SPECIFIC (0x72) - * - * List deviceIdData - * Short deviceIdDataFormat - * Short deviceIdDataLengthIndicator - * Short deviceIdType - * - */ -def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) { - logTrace "deviceIdData: ${cmd.deviceIdData}" - logTrace "deviceIdDataFormat: ${cmd.deviceIdDataFormat}" - logTrace "deviceIdDataLengthIndicator:${cmd.deviceIdDataLengthIndicator}" - logTrace "deviceIdType: ${cmd.deviceIdType}" - - return updateDeviceInfo() -} - -/** - * COMMAND_CLASS_FIRMWARE_UPDATE_MD_V2 (0x7a) - * - * Integer checksum - * Integer firmwareId - * Integer manufacturerId - * - */ -def zwaveEvent(hubitat.zwave.commands.firmwareupdatemdv2.FirmwareMdReport cmd) { - state.deviceInfo['checksum'] = "${cmd.checksum}" - state.deviceInfo['firmwareId'] = "${cmd.firmwareId}" - - return updateDeviceInfo() -} - -/******************************************************************************* - * CAPABILITITES * - ******************************************************************************/ - -/** - * configure - Configures the parameters of the device - * - * Required for the "Configuration" capability - */ -def configure() { - logInfo "configure()" - if ("$refreshInterval" == "null" || "$minimumChangeWatts" == "null") { - logError "Some preferences are null, please go through the configuration page first" - return - } - - updateDeviceInfo() - - def switchAllMode = hubitat.zwave.commands.switchallv1.SwitchAllSet.MODE_INCLUDED_IN_THE_ALL_ON_ALL_OFF_FUNCTIONALITY - if (switchAll == "Disabled") { - switchAllMode = hubitat.zwave.commands.switchallv1.SwitchAllSet.MODE_EXCLUDED_FROM_THE_ALL_ON_ALL_OFF_FUNCTIONALITY - } else if (switchAll == "Off Enabled") { - switchAllMode = hubitat.zwave.commands.switchallv1.SwitchAllSet.MODE_EXCLUDED_FROM_THE_ALL_ON_FUNCTIONALITY_BUT_NOT_ALL_OFF - } else if (switchAll == "On Enabled") { - switchAllMode = hubitat.zwave.commands.switchallv1.SwitchAllSet.MODE_EXCLUDED_FROM_THE_ALL_OFF_FUNCTIONALITY_BUT_NOT_ALL_ON - } - - logTrace "forceStateChangeOnReport value: " + forceStateChangeOnReport - logTrace "switchAll value: " + switchAll - - def reportGroup; - reportGroup = ("$includeVoltageInReport" == "true" ? 1 : 0) - reportGroup += ("$includeCurrentInReport" == "true" ? 2 : 0) - reportGroup += ("$includeWattInReport" == "true" ? 4 : 0) - reportGroup += ("$includeCurrentUsageInReport" == "true" ? 8 : 0) - - logTrace "setting configuration refresh interval: " + new BigInteger("$refreshInterval") - - /*************************************************************** - Device specific configuration parameters - ---------------------------------------------------------------- - Param Size Default Description - ------- ------- ------- ---------------------------------------- - 0x03 (3) 1 0 Current Overload Protection. Load will be closed when the Current overrun (US: 15.5A, other country: 16.2A) and the - time more than 2 minutes (0=disabled, 1=enabled). - 0x14 (20) 1 0 Configure the output load status after re-power on (0=last status, 1=always on, 2=always off) - 0x21 (33) 4 Set the RGB LED color value for testing. alternate rgb color level ie res,blue,green,red ie 00ffffff - 0x50 (80) 1 0 Enable to send notifications to associated devices in Group 1 when load changes (0=nothing, 1=hail CC, 2=basic CC report) - 0x51 (81) 1 0 mode 0 - energy, 1 - momentary indicator, 2 - night light - 0x53 (83) 3 0 hex value ffffff00 .. only night light mode - 0x54 (84) 1 50 dimmer level 0 -100 (doesn't work in night light mode) - 0x5A (90) 1 1 Enables/disables parameter 0x5A and 0x5B below - 0x5B (91) 2 25 The value here represents minimum change in wattage (in terms of wattage) for a REPORT to be sent (default 50W, size 2 bytes). - 0x5C (92) 1 5 The value here represents minimum change in wattage (in terms of percentage) for a REPORT to be sent (default 10%, size 1 byte). - 0x65 (101) 4 0x00 00 00 04 Which reports need to send in Report group 1 - 0x66 (102) 4 0x00 00 00 08 Which reports need to send in Report group 2 - 0x67 (103) 4 0 Which reports need to send in Report group 3 - 0x6F (111) 4 0x00 00 02 58 The time interval in seconds for sending Report group 1 (Valid values 0x01-0x7FFFFFFF). - 0x70 (112) 4 0x00 00 02 58 The time interval in seconds for sending Report group 2 (Valid values 0x01-0x7FFFFFFF). - 0x71 (113) 4 0x00 00 02 58 The time interval in seconds for sending Report group 3 (Valid values 0x01-0x7FFFFFFF). - 0xC8 (200) 1 0 Partner ID - 0xFC (252) 1 0 Enable/disable Configuration Locked (0 =disable, 1 =enable). - 0xFE (254) 2 0 Device Tag. - 0xFF (255) 1 N/A Reset to factory default setting - - - Configuration Values for parameters 0x65-0x67: - BYTE | 7 6 5 4 3 2 1 0 - =============================== - MSB 0 | 0 0 0 0 0 0 0 0 - Val 1 | 0 0 0 0 0 0 0 0 - VAL 2 | 0 0 0 0 0 0 0 0 - LSB 3 | 0 0 0 0 A B C 0 - - Bit A - Send Meter REPORT (for kWh) at the group time interval - Bit B - Send Meter REPORT (for watt) at the group time interval - Bit C - Automatically send(1) or don't send(0) Multilevel Sensor Report Command - ***************************************************************/ - - delayBetween([ - formatCommand(zwave.switchAllV1.switchAllSet(mode: switchAllMode)), -//// formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x14, size: 1, scaledConfigurationValue: 1)), //LED Status after Power On (0=Last Status, 1=Always On, 2=Always Off) - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x50, size: 1, scaledConfigurationValue: 0)), //Enable to send notifications to associated devices when load changes (0=nothing, 1=hail CC, 2=basic CC report) - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x5A, size: 1, scaledConfigurationValue: ("$onlySendReportIfValueChange" == "true" ? 1 : 0))), //Enables parameter 0x5B and 0x5C (0=disabled, 1=enabled) - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x5B, size: 2, scaledConfigurationValue: new BigInteger("$minimumChangeWatts"))), //Minimum change in wattage for a REPORT to be sent (Valid values 0 - 60000) - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x5C, size: 1, scaledConfigurationValue: new BigInteger("$minimumChangePercent"))), //Minimum change in percentage for a REPORT to be sent (Valid values 0 - 100) - - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x65, size: 4, scaledConfigurationValue: reportGroup)), //Which reports need to send in Report group 1 - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x66, size: 4, scaledConfigurationValue: 0)), //Which reports need to send in Report group 2 - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x67, size: 4, scaledConfigurationValue: 0)), //Which reports need to send in Report group 3 - - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x6F, size: 4, scaledConfigurationValue: new BigInteger("$refreshInterval"))), // change reporting time - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x70, size: 4, scaledConfigurationValue: new BigInteger(0xFFFFF))), - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x71, size: 4, scaledConfigurationValue: new BigInteger(0xFFFFF))), - - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0x3, size: 1, scaledConfigurationValue: ("$overloadProtection" == "true" ? 1 : 0))), // Current Overload Protection. - ], 200) -} - -/** - * on - Turns on the switch - * - * Required for the "Switch" capability - */ -def on() { - if (switchDisabled) { - logDebug "switch disabled, doing nothing" - delayBetween([ - formatCommand(zwave.switchBinaryV1.switchBinaryGet()) - ], 200) - } else { - logDebug "switching it on" - delayBetween([ - formatCommand(zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF)), - formatCommand(zwave.switchBinaryV1.switchBinaryGet()) - ], 200) - } -} - -/** - * off - Turns off the switch - * - * Required for the "Switch" capability - */ -def off() { - if (switchDisabled) { - logDebug "switch disabled, doing nothing" - delayBetween([ - formatCommand(zwave.switchBinaryV1.switchBinaryGet()) - ], 200) - } else { - logDebug "switching it off" - delayBetween([ - formatCommand(zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00)), - formatCommand(zwave.switchBinaryV1.switchBinaryGet()) - ], 200) - } -} - -/** - * poll - Polls the device - * - * Required for the "Polling" capability - */ -def poll() { - logTrace "poll()" - - delayBetween([ - formatCommand(zwave.switchBinaryV1.switchBinaryGet()), - formatCommand(zwave.meterV3.meterGet(scale: 0)), // energy kWh - formatCommand(zwave.meterV3.meterGet(scale: 1)), // energy kVAh - formatCommand(zwave.meterV3.meterGet(scale: 2)), // watts - formatCommand(zwave.meterV3.meterGet(scale: 4)), // volts - formatCommand(zwave.meterV3.meterGet(scale: 5)), // amps - ], 200) -} - -/** - * refresh - Refreshed values from the device - * - * Required for the "Refresh" capability - */ -def refresh() { - logInfo "refresh()" - updateDeviceInfo() - - sendEvent(name: "power", value: "0", displayed: true, unit: "W") - sendEvent(name: "energy", value: "0", displayed: true, unit: "kWh") - sendEvent(name: "amperage", value: "0", displayed: true, unit: "A") - sendEvent(name: "voltage", value: "0", displayed: true, unit: "V") - - sendEvent(name: "currentEnergyCostHour", value: "0", displayed: true) - sendEvent(name: "currentEnergyCostWeek", value: "0", displayed: true) - sendEvent(name: "currentEnergyCostMonth", value: "0", displayed: true) - sendEvent(name: "currentEnergyCostYear", value: "0", displayed: true) - - sendEvent(name: "cumulativeEnergyCostHour", value: "0", displayed: true) - sendEvent(name: "cumulativeEnergyCostWeek", value: "0", displayed: true) - sendEvent(name: "cumulativeEnergyCostMonth", value: "0", displayed: true) - sendEvent(name: "cumulativeEnergyCostYear", value: "0", displayed: true) - - delayBetween([ - formatCommand(zwave.switchMultilevelV1.switchMultilevelGet()), - formatCommand(zwave.meterV3.meterGet(scale: 0)), // energy kWh - formatCommand(zwave.meterV3.meterGet(scale: 1)), // energy kVAh - formatCommand(zwave.meterV3.meterGet(scale: 2)), // watts - formatCommand(zwave.meterV3.meterGet(scale: 4)), // volts - formatCommand(zwave.meterV3.meterGet(scale: 5)), // amps - formatCommand(zwave.configurationV1.configurationGet(parameterNumber: 0x51)), // device state - formatCommand(zwave.configurationV1.configurationGet(parameterNumber: 0x53)), // night light RGB value - formatCommand(zwave.configurationV1.configurationGet(parameterNumber: 0x54)), // led brightness - ], 200) -} - -/******************************************************************************* - * Methods * - ******************************************************************************/ - -/** - * installed - Called when the device handling is being installed - */ -def installed() { - logInfo "installed() called" - - if (state.deviceInfo == null) { - state.deviceInfo = [:] - state.deviceInfo['secureInclusion'] = false - } - - // Call a reset upon install to clear all values. - reset(); - - updateDeviceInfo(); -} - -/** - * updated - Called when the preferences of the device type are changed - */ -def updated() { - logInfo "updated()" - - updateStatus() - //updatePowerStatus(0) - response(configure()) -} - -/** - * reset - Resets the devices energy usage meter and attempt to reset device - * - * Defined by the custom command "reset" - */ -def reset() { - logInfo "reset()" - state.energyMeterRuntimeStart = now() - - delayBetween([ - formatCommand(zwave.meterV3.meterReset()), - formatCommand(zwave.meterV3.meterGet(scale: 0)), // energy kWh - formatCommand(zwave.meterV3.meterGet(scale: 1)), // energy kVAh - formatCommand(zwave.meterV3.meterGet(scale: 2)), // watts - formatCommand(zwave.meterV3.meterGet(scale: 4)), // volts - formatCommand(zwave.meterV3.meterGet(scale: 5)), // amps - ], 200) -} - -def factoryReset() { - logDebug "factoryReset()" - - formatCommand(zwave.configurationV1.configurationSet(parameterNumber: 0xFF, size: 4, scaledConfigurationValue: 1)) - //factory reset - configure() -} - -def getDeviceInfo() { - logDebug "getDeviceInfo()" - - delayBetween([ - formatCommand(zwave.versionV1.versionGet()), - formatCommand(zwave.firmwareUpdateMdV2.firmwareMdGet()), - //zwave.manufacturerSpecificV2.deviceSpecificGet().format(), - formatCommand(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) - ], 200) -} - -private updateStatus() { - def sinceTime = '' - if (state.energyMeterRuntimeStart != null) { - sinceTime = "${getBatteryRuntime()}" - } else { - sinceTime = now() - } - - sendEvent(name: "energyMeterRuntime", value: "$sinceTime", displayed: false) -} - -private updateDeviceInfo() { - logInfo "updateDeviceInfo()" - - if (state.deviceInfo == null) { - state.deviceInfo = [:] - } - - def buffer = "Get Device Info"; - def newBuffer = null; - - def switchStatus = "SWITCH ENABLED\n" - if (switchDisabled) { - switchStatus = "SWITCH DISABLED\n" - } - - if (state.deviceInfo['applicationVersion'] == null || - state.deviceInfo['manufacturerName'] == null) { - getDeviceInfo() - } else { - newBuffer = "${switchStatus}" - } - - if (state.deviceInfo['applicationVersion'] != null) { - if (newBuffer == null) { - newBuffer = "${switchStatus}" - } - - newBuffer += "app Version: ${state.deviceInfo['applicationVersion']} Sub Version: ${state.deviceInfo['applicationSubVersion']}\n"; - newBuffer += "zWaveLibrary Type: ${state.deviceInfo['zWaveLibraryType']}\n"; - newBuffer += "zWaveProtocol Version: ${state.deviceInfo['zWaveProtocolVersion']} Sub Version: ${state.deviceInfo['zWaveProtocolSubVersion']}\n"; - newBuffer += "secure inclusion: ${state.deviceInfo['secureInclusion'] || secureInclusionOverride}\n"; - } - - if (state.deviceInfo['manufacturerName'] != null) { - if (newBuffer == null) { - newBuffer = "${switchStatus}" - } - - newBuffer += "manufacturer Name: ${state.deviceInfo['manufacturerName']}\n"; - newBuffer += "manufacturer Id: ${state.deviceInfo['manufacturerId']}\n"; - newBuffer += "product Id: ${state.deviceInfo['productId']} Type Id: ${state.deviceInfo['productTypeId']}\n"; - newBuffer += "firmwareId: ${state.deviceInfo['firmwareId']} checksum: ${state.deviceInfo['checksum']}\n"; - } - - if (newBuffer == null) { - newBuffer = buffer - } - - return sendEvent(name: "deviceInfo", value: "$newBuffer", displayed: false) -} - -private getBatteryRuntime() { - def currentmillis = now() - state.energyMeterRuntimeStart - def days = 0 - def hours = 0 - def mins = 0 - def secs = 0 - secs = (currentmillis / 1000).toInteger() - mins = (secs / 60).toInteger() - hours = (mins / 60).toInteger() - days = (hours / 24).toInteger() - secs = (secs - (mins * 60)).toString().padLeft(2, '0') - mins = (mins - (hours * 60)).toString().padLeft(2, '0') - hours = (hours - (days * 24)).toString().padLeft(2, '0') - - if (days > 0) { - return "$days days and $hours:$mins:$secs" - } else { - return "$hours:$mins:$secs" - } -} - -private getBatteryRuntimeInHours() { - def currentmillis = now() - state.energyMeterRuntimeStart - def days = 0 - def hours = 0 - def mins = 0 - def secs = 0 - secs = (currentmillis / 1000) - mins = (secs / 60) - hours = (mins / 60) - return hours -} - -void logInfo(str) { - log.info str -} - -void logWarn(str) { - log.warn str -} - -void logError(str) { - log.error str -} - -void logDebug(str) { - if (isLogLevelDebug) { - log.debug str - } -} - -void logTrace(str) { - if (isLogLevelTrace) { - log.trace str - } -} - -def energy() { - logDebug "in set energy mode" - sendEvent(name: "deviceMode", value: "energy", displayed: true) - setDeviceMode(0) -} - -def formatCommand(hubitat.zwave.Command cmd) { - if (isSecured()) { - logTrace "Formatting secured command: ${cmd}" - zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() - } else { - logTrace "Formatting unsecured command: ${cmd}" - cmd.format() - } -} - -def isSecured() { - (state.deviceInfo && state.deviceInfo['secureInclusion']) || secureInclusionOverride -} - -def internalCreateEvent(event) { - if (forceStateChangeOnReport) { - event.isStateChange = true - } - - return createEvent(event) -} diff --git a/Fortrezz-Mimo2-Child.groovy b/Fortrezz-Mimo2-Child.groovy deleted file mode 100644 index 57bd494..0000000 --- a/Fortrezz-Mimo2-Child.groovy +++ /dev/null @@ -1,110 +0,0 @@ -/** - * FortrezZ MIMO2+ Child - * - * Copyright 2016 FortrezZ, LLC - * - * 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. - * - * Version 1.1 - 5/3/19 - * Basic Port from SmartThings - * Can you port a driver that it just a glorified TODO list? - * - * Version 1.2 - 5/6/19 - peng1can - * Very basic but working b-side child driver. - * - * Version 2.0 - 5/9/19 - peng1can - * Overhaul to parent/child device - * - */ -metadata { - definition (name: "FortrezZ MIMO2+ Child Relay", namespace: "peng1can", author: "D Canfield") { - capability "Contact Sensor" - capability "Relay Switch" - capability "Switch" - capability "Voltage Measurement" - capability "Refresh" - } - - tiles { - standardTile("switch", "device.switch", width: 2, height: 2) { - state "on", label: "Relay On", action: "off", backgroundColor: "#53a7c0" - state "off", label: "Relay Off", action: "on", backgroundColor: "#ffffff" - } - standardTile("anaDig", "device.anaDig", inactiveLabel: false) { - state "open", label: '${name}', backgroundColor: "#ffa81e" - state "closed", label: '${name}', backgroundColor: "#79b821" - state "val", label:'${currentValue}v', unit:"", defaultState: true - } - standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { - state "default", label:'', action:"refresh.refresh" - } - standardTile("powered", "device.powered", inactiveLabel: false) { - state "powerOn", label: "Power On", backgroundColor: "#79b821" - state "powerOff", label: "Power Off", backgroundColor: "#ffa81e" - } - standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { - state "configure", label:'', action:"configuration.configure" - } - standardTile("blank", "device.blank", inactiveLabel: true, decoration: "flat") { - state("blank", label: '') - } - main (["switch"]) - details(["switch", "anaDig", "blank", "blank", "refresh", "powered"]) - } -} - -// parse events into attributes -def parse(String description) { - log.debug "Parsing '${description}'" - // TODO: handle 'contact' attribute - // TODO: handle 'switch' attribute - // TODO: handle 'switch' attribute - // TODO: handle 'voltage' attribute - -} - -def eventParse(evt) { - log.debug("Event: ${evt.name}=${evt.value}") - switch(evt.name) { - case "powered": - sendEvent(name: evt.name, value: evt.value) - break - case "switch": - sendEvent(name: "switch", value: evt.value) - break - case "contact": - sendEvent(name: "contact", value: evt.value) - break - case "voltage": - sendEvent(name: "voltage", value: evt.value) - break - case "relay": - sendEvent(name: evt.name, value: evt.value) - break -// case "anaDig": -// sendEvent(name: "anaDig1", value: evt.value) - // break - } -} - -// handle commands -def on() { - parent.on(device.deviceNetworkId) - log.debug("Executing 'on'") -} - -def off() { - parent.off(device.deviceNetworkId) - log.debug("Executing 'off'") -} -def refresh() { - parent.refresh() - log.debug("Executing 'refresh'") -} diff --git a/Fortrezz-Mimo2.groovy b/Fortrezz-Mimo2.groovy deleted file mode 100644 index fe67ec9..0000000 --- a/Fortrezz-Mimo2.groovy +++ /dev/null @@ -1,416 +0,0 @@ -/** - * MIMO2 Device Handler - * - * Copyright 2016 FortrezZ, LLC - * Copyright 2019 D Canfield - * - * 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. - * - * Ported from SmartThings by peng1can - * - * Version 1.1 - 5/3/19 - peng1can - * Basic Port from SmartThings - * - * Version 1.2 - 5/6/19 - peng1can - * Fixed the basic parent/child code - * - * Version 2.0 - 5/6/19 - peng1can - * Overhaul to make two separate child devices for A side and B side - * - */ -metadata { - definition (name: "FortrezZ MIMO2+", namespace: "peng1can", author: "D Canfield") { - capability "Alarm" - capability "Contact Sensor" - capability "Switch" - capability "Voltage Measurement" - capability "Configuration" - capability "Refresh" - - attribute "powered", "string" - attribute "relay", "string" - attribute "relay2", "string" - attribute "contact2", "string" - attribute "voltage2", "string" - - command "on" - command "off" - command "on2" - command "off2" - command "recreateChildDevices" - command "deleteChildren" - - fingerprint deviceId: "0x2100", inClusters: "0x5E,0x86,0x72,0x5A,0x59,0x71,0x98,0x7A" - } - - preferences { - - input ("RelaySwitchDelay", "decimal", title: "Delay between relay switch on and off in seconds. Only Numbers 0 to 3 allowed. 0 value will remove delay and allow relay to function as a standard switch:\nRelay 1", description: "Numbers 0 to 3.1 allowed.", defaultValue: 0, required: false, displayDuringSetup: true) - input ("RelaySwitchDelay2", "decimal", title: "Relay 2", description: "Numbers 0 to 3.1 allowed.", defaultValue: 0, required: false, displayDuringSetup: true) - input ("Sig1AD", "bool", title: "Switch off for digital, on for analog:\nSIG1", required: false, displayDuringSetup: true) - input ("Sig2AD", "bool", title: "SIG2", required: false, displayDuringSetup: true) - } // the range would be 0 to 3.1, but the range value would not accept 3.1, only whole numbers (i tried paranthesis and fractions too. :( ) - - - tiles { - standardTile("switch", "device.switch", width: 2, height: 2) { - state "on", label: "Relay 1 On", action: "off", backgroundColor: "#53a7c0" - state "off", label: "Relay 1 Off", action: "on", backgroundColor: "#ffffff" - } - standardTile("switch2", "device.switch2", width: 2, height: 2, inactiveLabel: false) { - state "on", label: "Relay 2 On", action: "off2", backgroundColor: "#53a7c0" - state "off", label: 'Relay 2 Off', action: "on2", backgroundColor: "#ffffff" - } - standardTile("anaDig1", "device.anaDig1", inactiveLabel: false) { - state "open", label: '${name}', backgroundColor: "#ffa81e" - state "closed", label: '${name}', backgroundColor: "#79b821" - state "val", label:'${currentValue}v', unit:"", defaultState: true - } - standardTile("anaDig2", "device.anaDig2", inactiveLabel: false) { - state "open", label: '${name}', backgroundColor: "#ffa81e" - state "closed", label: '${name}', backgroundColor: "#79b821" - state "val", label:'${currentValue}v', unit:"", defaultState: true - } - standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { - state "default", label:'', action:"refresh.refresh" - } - standardTile("powered", "device.powered", inactiveLabel: false) { - state "powerOn", label: "Power On", backgroundColor: "#79b821" - state "powerOff", label: "Power Off", backgroundColor: "#ffa81e" - } - standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { - state "configure", label:'', action:"configuration.configure" - } - standardTile("blank", "device.blank", inactiveLabel: true, decoration: "flat") { - state("blank", label: '') - } - main (["switch"]) - details(["switch", "anaDig1", "blank", "switch2", "anaDig2", "blank", "configure", "refresh", "powered"]) - } -} - -// parse events into attributes -def parse(String description) { - def result = null - def cmd = zwave.parse(description) - - if (cmd.CMD == "7105") { //Mimo sent a power loss report - log.debug "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) { - def eventReturn = zwaveEvent(cmd) - if(eventReturn in hubitat.device.HubMultiAction) { - result = eventReturn - } - else { - result = createEvent(eventReturn) - } - } - log.debug "Parse returned ${result} $cmd.CMD" - return result -} - -def installed() { - createChildDevices() -} - -def recreateChildDevices() { - log.debug "recreateChildDevices" - deleteChildren() - createChildDevices() -} - -def deleteChildren() { - log.debug "deleteChildren" - def children = getChildDevices() - - children.each {child-> - deleteChildDevice(child.deviceNetworkId) - } -} - -private void createChildDevices() { - log.debug "createChildDevices" - addChildDevice("FortrezZ MIMO2+ Child Relay","${device.deviceNetworkId}.A", - [label: "${device.displayName} A-Side", isComponent: true, name: "A-Side"]) - addChildDevice("FortrezZ MIMO2+ Child Relay","${device.deviceNetworkId}.B", - [label: "${device.displayName} B-Side", isComponent: true, name: "B-Side"]) -} - -def updated() { - if (!childDevices) { - createChildDevices() - } - // Test and possibly remove the state.count bit for Hubitat (original dev had it here for SmartThings): - if (state.count == 1) // this bit with state keeps the function from running twice ( which it always seems to want to do) (( oh, and state.count is a variable which is nonVolatile and doesn't change per every parse request. - { - state.count = 0 - log.debug "Settings Updated..." - configure() - refresh() - return -// return response(delayBetween([configure(),refresh()],200)) // hubitat can only delayBetween zwave commands - } - else {state.count = 1} -} - -def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) // basic set is essentially our digital sensor for SIG1 and SIG2 - it doesn't use an endpoint so we are having it send a multilevelGet() for SIG1 and SIG2 to see which one triggered. -{ - log.debug "sent a BasicSet command" - return response(refresh()) -} - -def zwaveEvent(int endPoint, hubitat.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) // event to get the state of the digital sensor SIG1 and SIG2 -{ - log.debug "sent a sensorBinaryReport command" - return response(refresh()) -} - -def zwaveEvent(int endPoint, hubitat.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) // event for seeing the states of relay 1 and relay 2 -{ - def map = [:] // map for containing the name and state of the specified relay - map.name = "switch" - def childDevice - if (cmd.value) // possible values are 255 and 0 (0 is false) - {map.value = "on"} - else - {map.value = "off"} - if (endPoint == 3) - { - childDevice = getChildDevice("${device.deviceNetworkId}.A") - pname = "switch" - } - else if (endPoint == 4) - { - childDevice = getChildDevice("${device.deviceNetworkId}.B") - pname = "switch2" - } - log.debug "sent a SwitchBinary command $map.name $map.value" // the map is for debug messages and children, not for the return command to the parent - childDevice.sendEvent(map) - return [name: "$pname", value: cmd.value ? "on" : "off"] -} - - -def zwaveEvent (int endPoint, hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) // sensorMultilevelReport is used to report the value of the analog voltage for SIG1 -{ - def map = [:] - def stdEvent = [:] - def voltageVal = CalculateVoltage(cmd.scaledSensorValue) // saving the scaled Sensor Value used to enter into a large formula to determine actual voltage value - def childDevice - - if (endPoint == 1) //endPoint 1 is for SIG1 - { - log.debug "endPoint 1" - childDevice = getChildDevice("${device.deviceNetworkId}.A") - pname = "anaDig1" - } - else if (endPoint == 2 ) //endPoint 2 is for SIG2 - { - log.debug "endPoint 2" - childDevice = getChildDevice("${device.deviceNetworkId}.B") - pname = "anaDig2" - } - - if (state.AD1 == false) // state.AD1 is to determine which state the anaDig1 tile should be in (either analogue or digital mode) - { - map.name = "$pname" - stdEvent.name = "contact" - if (voltageVal < 2) { // DK changed to 2v to follow LED behavior - map.value = "closed" - stdEvent.value = "closed" - } - else - { - map.value = "open" - stdEvent.value = "open" - } - } - else //or state.AD1 is true for analogue mode - { - map.name = "$pname" - stdEvent.name = "voltage" - map.value = voltageVal - stdEvent.value = voltageVal - map.unit = "v" - stdEvent.unit = "v" - } - - //log.debug map.name map.value - childDevice.sendEvent(stdEvent) - log.debug "sent a SensorMultilevelReport $map.name $map.value" - sendEvent(stdEvent) //Might need removed - return map -} - -def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { //standard security encapsulation event code (should be the same on all device handlers) - def encapsulatedCommand = cmd.encapsulatedCommand() - // can specify command class versions here like in zwave.parse - if (encapsulatedCommand) { - return zwaveEvent(encapsulatedCommand) - } -} - -// MultiChannelCmdEncap and MultiInstanceCmdEncap are ways that devices -// can indicate that a message is coming from one of multiple subdevices -// or "endpoints" that would otherwise be indistinguishable -def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) { - def encapsulatedCommand = cmd.encapsulatedCommand() - log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}") - - if (encapsulatedCommand) { - return zwaveEvent(cmd.sourceEndPoint, encapsulatedCommand) - } -} - -def zwaveEvent(int endPoint, hubitat.zwave.commands.multichannelassociationv2.MultiChannelAssociationReport cmd) { - - log.debug "sent an Association Report" - log.debug " ${cmd.groupingIdentifier}" - //return [:] -} - -def zwaveEvent(hubitat.zwave.Command cmd) { - // Handles all Z-Wave commands we aren't interested in - log.debug("Un-parsed Z-Wave message ${cmd}") - return [:] -} - -def CalculateVoltage(ADCvalue) // used to calculate the voltage based on the collected Scaled sensor value of the multilevel sensor event -{ - def volt = (((2.396*(10**-17))*(ADCvalue**5)) - ((1.817*(10**-13))*(ADCvalue**4)) + ((5.087*(10**-10))*(ADCvalue**3)) - ((5.868*(10**-7))*(ADCvalue**2)) + ((9.967*(10**-4))*(ADCvalue)) - (1.367*(10**-2))) - return volt.round(1) -} - - -def configure() { - log.debug "Configuring...." - def sig1 - def sig2 - if (Sig1AD == true) - { sig1 = 0x01 - state.AD1 = true} - else if (Sig1AD == false) - { sig1 = 0x40 - state.AD1 = false} - if (Sig2AD == true) - { sig2 = 0x01 - state.AD2 = true} - else if (Sig2AD == false) - { sig2 = 0x40 - state.AD2 = false} - - def delay = (RelaySwitchDelay*10).toInteger() // the input which we get from the user is a string and is in seconds while the MIMO2 configuration requires it in 100ms so - change to integer and multiply by 10 - def delay2 = (RelaySwitchDelay2*10).toInteger() // the input which we get from the user is a string and is in seconds while the MIMO2 configuration requires it in 100ms so - change to integer and multiply by 10 - if (delay > 31) - { - log.debug "Relay 1 input ${delay / 10} set too high. Max value is 3.1" - delay = 31 - } - if (delay < 0) - { - log.debug "Relay 1 input ${delay / 10} set too low. Min value is 0" - delay = 0 - } - if (delay2 > 31) - { - log.debug "Relay 2 input ${delay2 / 10} set too high. Max value is 3.1" - delay2 = 31 - } - if (delay2 < 0) - { - log.debug "Relay 2 input ${delay2 / 10} set too low. Min value is 0" - delay = 0 - } - - - - return delayBetween([ - encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]), 0), - encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]), 0), - - encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]), 1), - encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]), 2), - encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]), 3), - encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]), 4), - - secure(zwave.configurationV1.configurationSet(configurationValue: [sig1], parameterNumber: 3, size: 1)), // sends a multiLevelSensor report every 30 seconds for SIG1 - secure(zwave.configurationV1.configurationSet(configurationValue: [sig2], parameterNumber: 9, size: 1)), // sends a multiLevelSensor report every 30 seconds for SIG2 - secure(zwave.configurationV1.configurationSet(configurationValue: [delay], parameterNumber: 1, size: 1)), // configurationValue for parameterNumber means how many 100ms do you want the relay - // to wait before it cycles again / size should just be 1 (for 1 byte.) - secure(zwave.configurationV1.configurationSet(configurationValue: [delay2], parameterNumber: 2, size: 1)), - - ], 200) -} - -def on(child) { - log.debug "On $child" - if (child == "${device.deviceNetworkId}.B") { - return encap(zwave.basicV1.basicSet(value: 0xff), 4) // physically changes the relay from on to off and requests a report of the relay - } else { - return encap(zwave.basicV1.basicSet(value: 0xff), 3) // Use switch 1 if it's child A or If there's no child defined (the button was pressed on the parent device) - } -} - -def off(child) { - log.debug "Off $child" - if (child == "${device.deviceNetworkId}.B") { - return encap(zwave.basicV1.basicSet(value: 0x00), 4) // physically changes the relay from on to off and requests a report of the relay - } else { - return encap(zwave.basicV1.basicSet(value: 0x00), 3) // Same as above - } -} - -def on2() { - return encap(zwave.basicV1.basicSet(value: 0xff), 4) - // on2 and off2 should now only be reachable in the parent interface. -} - -def off2() { - return encap(zwave.basicV1.basicSet(value: 0x00), 4) -} - -def refresh() { - log.debug "Refresh" - return delayBetween([ - encap(zwave.sensorMultilevelV5.sensorMultilevelGet(), 1),// requests a report of the anologue input voltage for SIG1 - encap(zwave.sensorMultilevelV5.sensorMultilevelGet(), 2),// requests a report of the anologue input voltage for SIG2 - encap(zwave.switchBinaryV1.switchBinaryGet(), 3), //requests a report of the relay to make sure that it changed for Relay 1 - encap(zwave.switchBinaryV1.switchBinaryGet(), 4), //requests a report of the relay to make sure that it changed for Relay 2 - ],200) -} - -def refreshZWave() { - log.debug "Refresh (Z-Wave Response)" - return delayBetween([ - encap(zwave.sensorMultilevelV5.sensorMultilevelGet(), 1),// requests a report of the anologue input voltage for SIG1 - encap(zwave.sensorMultilevelV5.sensorMultilevelGet(), 2),// requests a report of the anologue input voltage for SIG2 - encap(zwave.switchBinaryV1.switchBinaryGet(), 3), //requests a report of the relay to make sure that it changed for Relay 1 - encap(zwave.switchBinaryV1.switchBinaryGet(), 4) //requests a report of the relay to make sure that it changed for Relay 2 - ],200) -} - -private secureSequence(commands, delay=200) { // decided not to use this - return delayBetween(commands.collect{ secure(it) }, delay) -} - -private secure(hubitat.zwave.Command cmd) { //take multiChannel message and securely encrypts the message so the device can read it - return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() -} - -private encap(cmd, endpoint) { // takes desired command and encapsulates it by multiChannel and then sends it to secure() to be wrapped with another encapsulation for secure encryption - if (endpoint) { - return secure(zwave.multiChannelV3.multiChannelCmdEncap(bitAddress: false, sourceEndPoint:0, destinationEndPoint: endpoint).encapsulate(cmd)) - } else { - return secure(cmd) - } -} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md index 794df3b..e3c1686 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ -# Hubitat +# Ring Integration +To use the Ring integration, first install the Ring_Integration app. Next install the Ring_Manager, Ring_Doorbell, Ring_Camera, and Ring_Battery_Camera drivers. -Aeotec HDSS - Port of the Aetoec Heavy Duty Smart Switch Driver
-Join API - Continuation of @stephhack's Join API Driver
-Fortrezz MIMO2 - Port of the 2-port Fortrezz MIMO2 Controller. +## Configuring Alexa +1. For each Ring device create a routine for Motion Detected +2. Set the routine to set the Ring Manager device to a specific unique dimmer level +3. Repeat the above 2 steps for Doorbell button pushes + +## Configure the App +1. Provide your Ring credentials +2. Select the devices you wish to configure for the integration +3. Provide the same dimmer values you specified in the Alexa routine for each setting in the app + +## Quick Summary +Basically, when the Alexa routine occurs, the Ring Manager device will be set to a specific dimmer level. The Ring Integration app will translate this to triggering motion or a button push for the appropriate ring virtual device. diff --git a/Virtual-Garage-Door-Child.groovy b/Virtual-Garage-Door-Child.groovy deleted file mode 100644 index b2bdfa6..0000000 --- a/Virtual-Garage-Door-Child.groovy +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Virtual Garage Door Manager - * - * Copyright 2017 Patrick McKinnon - * - * 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. - * - * Author: Patrick McKinnon (patrick@ojolabs.com) - */ - -definition( - name: "Virtual Garage Door", - namespace: "peng1can", - author: "peng1can@gmail.com", - description: "Manages state of 'Virtual Garage Door' device (Child App)", - category: "Convenience", - parent: "peng1can/parent:Virtual Garage Door Manager", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/garage_contact.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/garage_contact@2x.png" -) - -preferences { - section("Garage door actuator"){ - input "actuatorSwitch", "capability.switch", - title: "Garage Door Actuator Switch", - required: true - } - - section("Garage door multisensor"){ - input "contactSensor", "capability.contactSensor", - title: "Garage Door Contact Sensor", - required: true - input "accelerationSensor", "capability.accelerationSensor", - title: "Garage Door Acceleration Sensor", - required: true - } - - section("Virtual garage door device"){ - input "garage", "capability.doorControl", - title: "Virtual Garage Door", - required: true - } -} - -def installed() { - initialize() -} - -def updated() { - unsubscribe() - initialize() -} - -private initialize() { - if(garage.supportedCommands.find { it.name == "setVirtualGarageState" }) { - subscribe(contactSensor, "contact", contactHandler) - subscribe(accelerationSensor, "acceleration", accelerationHandler) - subscribe(garage, "door", garageControlHandler) - subscribe(garage, "switch", garageSwitchHandler) - - state.direction = null - state.current = contactSensor.currentContact - - synchronize() - } - else { - log.error("Virtual Garage Door device should by of type pmckinnon/'Virtual Garage Door'") - } -} - -private synchronize() { - log.debug "synchronize, current: $state.current, direction: $state.direction" - - garage.setVirtualGarageState([ - door: state.direction ? state.direction : state.current - ]) -} - -def accelerationHandler(evt) { - log.debug "accelerationHandler($evt.value)" - onSensorChanged() -} - -def contactHandler(evt) { - log.debug "contactHandler($evt.value)" - onSensorChanged() -} - -private onSensorChanged() { - log.debug("onSensorChanged($contactSensor.currentContact, $accelerationSensor.currentAcceleration), current: $state.current, direction: $state.direction") - if(accelerationSensor.currentAcceleration == "inactive") { - // Motion has stabilized, save the current state - state.direction = null - state.current = contactSensor.currentContact - } - else if(!state.direction) { - // Door is moving because of outside event (button, opener, etc) - // Guess the direction based on the previously stable state - state.direction = (state.current == "open") ? "closing" : "opening" - } - - synchronize() -} - -def garageControlHandler(evt) { - log.debug "garageControlHandler($evt.value)" -} - -def garageSwitchHandler(evt) { - log.debug "garageSwitchHandler($evt.value), current: $state.current" - - // Only take action if our direction state has stabilized - if(state.direction == null) { - if(evt.value == "on") { - if(state.current != "open") { - state.direction = "opening" - triggerActuator() - } - } - else { - if(state.current != "closed") { - state.direction = "closing" - triggerActuator() - } - } - } - - synchronize() -} - -private triggerActuator() { - log.debug "triggerActuator()" - actuatorSwitch.on() -} diff --git a/Virtual-Garage-Door-Device.groovy b/Virtual-Garage-Door-Device.groovy deleted file mode 100644 index 7ac4d06..0000000 --- a/Virtual-Garage-Door-Device.groovy +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Virtual Garage Door - * - * Copyright 2017 Patrick McKinnon - * - * 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. - * - * Original Author: Patrick McKinnon (patrick@ojolabs.com) - * Hubitat patches from @stephack, @peng1can - */ -metadata { - definition (name: "Virtual Garage Door", namespace: "peng1can", author: "peng1can@gmail.com") { - capability "Actuator" - capability "Door Control" - capability "Contact Sensor" //added for HE dashboard - capability "Garage Door Control" - capability "Switch" - - command "setVirtualGarageState" - } - - simulator { - - } - - tiles { - standardTile("toggle", "device.door", width: 2, height: 2) { - state("closed", label:'${name}', action:"door control.open", icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821", nextState:"opening") - state("open", label:'${name}', action:"door control.close", icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e", nextState:"closing") - state("opening", label:'${name}', icon:"st.doors.garage.garage-closed", backgroundColor:"#ffe71e") - state("closing", label:'${name}', icon:"st.doors.garage.garage-open", backgroundColor:"#ffe71e") - - } - standardTile("open", "device.door", inactiveLabel: false, decoration: "flat") { - state "default", label:"open", action:"door control.open", icon:"st.doors.garage.garage-opening" - } - standardTile("close", "device.door", inactiveLabel: false, decoration: "flat") { - state "default", label:"close", action:"door control.close", icon:"st.doors.garage.garage-closing" - } - - main "toggle" - details(["toggle", "open", "close"]) - } -} - -def open() { - log.debug "open()" - sendEvent(name: "switch", value: "on") - sendEvent(name: "contact", value: "open") //added for HE dashboard -} - -def close() { - log.debug "close()" - sendEvent(name: "switch", value: "off") - sendEvent(name: "contact", value: "closed") //added for HE dashboard -} - -def setVirtualGarageState(config) { - log.debug("setVirtualGarageState($config)") - sendEvent(name: "door", value: config["door"]) - if(config["door"] == "open") { - sendEvent(name: "switch", value: "on") - sendEvent(name: "contact", value: "open") //not sure how this is used by app but added here because I assume it would be needed - } - else if(config["door"] == "closed") { - sendEvent(name: "switch", value: "off") - sendEvent(name: "contact", value: "closed") //not sure how this is used by app but added here because I assume it would be needed - } -} diff --git a/Virtual-Garage-Door-Parent.groovy b/Virtual-Garage-Door-Parent.groovy deleted file mode 100644 index ff2bc4b..0000000 --- a/Virtual-Garage-Door-Parent.groovy +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Virtual Garage Door Manager - * - * Copyright 2017 Patrick McKinnon - * Copyright 2017 D Canfield - * - * 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. - * - * Original Author: Patrick McKinnon (patrick@ojolabs.com) - * Hubitat Port, Parent/Child Mods: D Canfield (peng1can@gmail.com) - */ - -definition( - name: "Virtual Garage Door Manager", - namespace: "peng1can/parent", - author: "peng1can@gmail.com", - description: "Manages Instances of 'Virtual Garage Door' (Parent App)", - category: "Convenience", - singleInstance: true, - iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/garage_contact.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/garage_contact@2x.png" -) - - -preferences { - page(name: "mainPage", title: "Garge Doors", install: true, uninstall: true) { - section { - app(name: "virtualGarageDoor", appName: "Virtual Garage Door", namespace: "peng1can", title: "New Garage Door", multiple: true) - } - } -} diff --git a/apps/Ring_Integration.groovy b/apps/Ring_Integration.groovy new file mode 100644 index 0000000..289ffef --- /dev/null +++ b/apps/Ring_Integration.groovy @@ -0,0 +1,562 @@ +/** + * Ring Integration + * + * Copyright 2019 Dominick Meglio + * + */ +definition( + name: "Ring Integration", + namespace: "dcm.ring", + author: "Dominick Meglio", + description: "Integrate your Ring Doorbells and Cameras with Hubitat", + category: "My Apps", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", + iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") + + +preferences { + page(name: "prefApiAccess", title: "Ring") + page(name: "prefRingDevices", title: "Ring") + page(name: "prefRingTriggerSettings", title: "Ring") +} + +def prefApiAccess() { + return dynamicPage(name: "prefApiAccess", title: "Connect to Ring", nextPage: "prefRingDevices", uninstall:false, install: false) { + section("Ring Login Information"){ + input("ringUsername", "text", title: "Ring Username", description: "Enter your Ring username", required: true) + input("ringPassword", "password", title: "Ring Password", description: "Enter your Ring password", required: true) + input "debugOutput", "bool", title: "Enable debug logging?", defaultValue: true, displayDuringSetup: false, required: false + } + } +} + +def prefRingDevices() { + if (!getRingDevices()) + { + return dynamicPage(name: "prefRingDevices", title: "Login Error", install: false, uninstall: false) { + section("Error") { + paragraph "The specified username/password are invalid" + } + } + } + else + { + return dynamicPage(name: "prefRingDevices", title: "Ring Devices", nextPage: "prefRingTriggerSettings", install: false, uninstall: false) { + section("Select your Ring Devices") { + if (state.doorbells.size() > 0) + input(name: "doorbells", type: "enum", title: "Doorbells", required:false, multiple:true, options:state.doorbells, hideWhenEmpty: true) + if (state.cameras.size() > 0) + input(name: "cameras", type: "enum", title: "Cameras", required:false, multiple:true, options:state.cameras, hideWhenEmpty: true) + } + } + } +} + +def prefRingTriggerSettings() { + return dynamicPage(name: "prefRingTriggerSettings", title: "Ring Device Trigger Settings", install: true, uninstall: true) { + section("Triggers") { + paragraph "Specify the trigger value used to signal each type of event. These will be the dimmer values set by your Alexa routine." + for (doorbell in doorbells) { + input(name: "doorbellMotionTrigger${doorbell}", type: "number", title: "Motion Trigger for ${state.doorbells[doorbell]}", required: true) + input(name: "doorbellButtonTrigger${doorbell}", type: "number", title: "Button Trigger for ${state.doorbells[doorbell]}", required: true) + } + for (camera in cameras) { + input(name: "cameraMotionTrigger${camera}", type: "number", title: "Motion Trigger for ${state.cameras[camera]}", required: true) + } + } + } +} + +def installed() { + logDebug "Installed with settings: ${settings}" + + initialize() +} + +def updated() { + logDebug "Updated with settings: ${settings}" + unschedule() + unsubscribe() + initialize() +} + +def uninstalled() { + logDebug "Uninstalled app" + for (device in getChildDevices()) + { + deleteChildDevice(device.deviceNetworkId) + } +} + +def initialize() { + logDebug "initializing" + cleanupChildDevices() + createChildDevices() + cleanupSettings() + runEvery5Minutes(updateDevices) + schedule("0/30 * * * * ? *", updateDevices) +} + +def updateDevices() +{ + logDebug "refreshing ring devices" + def authToken = getAuthToken(state.token) + if (!authToken) return false + + def params = [ + uri: "https://api.ring.com", + path: "/clients_api/ring_devices", + query: [ + api_version: "9", + "auth_token": authToken + ] + ] + try + { + httpGet(params) { resp -> + for (camera in resp.data.stickup_cams) { + def cameraDevice = getChildDevice("ring:" + camera.id) + if (camera.led_status == "off") + { + cameraDevice.sendEvent(name: "switch", value: "off") + } + else if (camera.led_status == "on") + { + cameraDevice.sendEvent(name: "switch", value: "on") + } + else if (camera.led_status.seconds_remaining.toInteger() > 0) + { + cameraDevice.sendEvent(name: "switch", value: "on") + } + else + { + cameraDevice.sendEvent(name: "switch", value: "off") + } + + if (camera.siren_status != null) + { + if (camera.siren_status.seconds_remaining.toInteger() > 0) + { + if (cameraDevice.getDataValue("strobing") == "true") + cameraDevice.sendEvent(name: "alarm", value: "both") + else + cameraDevice.sendEvent(name: "alarm", value: "siren") + + } + else + { + if (cameraDevice.getDataValue("strobing") == "true") + cameraDevice.sendEvent(name: "alarm", value: "strobe") + else + cameraDevice.sendEvent(name: "alarm", value: "off") + } + } + + if (camera.battery_voltage != null || camera.battery_voltage_2 != null) + { + def batteryLevel = 0 + if (camera.battery_life != null && camera.battery_life_2 != null) + batteryLevel = (camera.battery_life.toInteger() + camera.battery_life_2.toInteger())/2 + else if (camera.battery_life != null) + batteryLevel = camera.battery_life.toInteger() + else if (camera.battery_life_2 != null) + batteryLevel = camera.battery_life_2.toInteger() + + cameraDevice.sendEvent(name: "battery", value: batteryLevel) + } + } + } + } + catch (e) + { + logDebug e + } +} + +def getRingDevices() { + state.doorbells = [:] + state.cameras = [:] + state.cameraDetails = [:] + + def token = login() + if (!token) return false + + def authToken = getAuthToken(token) + if (!authToken) return false + + def params = [ + uri: "https://api.ring.com", + path: "/clients_api/ring_devices", + query: [ + api_version: "9", + "auth_token": authToken + ] + ] + try + { + httpGet(params) { resp -> + for (doorbell in resp.data.doorbots) { + state.doorbells[doorbell.id] = doorbell.description + } + for (camera in resp.data.stickup_cams) { + state.cameras[camera.id] = camera.description + state.cameraDetails[camera.id] = [ + name: camera.description, + hasBattery: camera.kind.contains("stickup_cam") + ] + } + } + } + catch (e) + { + logDebug e + } + return true +} + +def createChildDevices() { + if (!getChildDevice("ring:manager")) + { + addChildDevice("ring", "Ring Manager", "ring:manager", 1234, ["name": "Ring Manager", isComponent: true]) + } + if (doorbells != null) + { + for (doorbell in doorbells) + { + if (!getChildDevice("ring:" + doorbell)) + addChildDevice("ring", "Ring Doorbell", "ring:" + doorbell, 1234, ["name": state.doorbells[doorbell], isComponent: false]) + } + } + + if (cameras != null) + { + for (camera in cameras) + { + if (!getChildDevice("ring:" + camera)) + { + if (state.cameraDetails[camera].hasBattery) + addChildDevice("ring", "Ring Battery Camera", "ring:" + camera, 1234, ["name": state.cameras[camera], isComponent: false]) + else + addChildDevice("ring", "Ring Camera", "ring:" + camera, 1234, ["name": state.cameras[camera], isComponent: false]) + } + } + } +} + +def cleanupChildDevices() +{ + for (device in getChildDevices()) + { + if (device.deviceNetworkId == "ring:manager") + continue + + def deviceId = device.deviceNetworkId.replace("ring:","") + + def deviceFound = false + for (doorbell in doorbells) + { + if (doorbell == deviceId) + { + deviceFound = true + break + } + } + + if (deviceFound == true) + continue + + for (camera in cameras) + { + if (camera == deviceId) + { + deviceFound = true + break + } + } + if (deviceFound == true) + continue + + deleteChildDevice(device.deviceNetworkId) + } +} + +def cleanupSettings() +{ + def allProperties = this.settings + def deviceName = null + + for (property in allProperties) { + if (property.key.startsWith("doorbellMotionTrigger")) { + deviceName = property.key.replace("doorbellMotionTrigger","") + if (!getChildDevice("ring:" + deviceName)) { + app.removeSetting(property.key) + } + } + else if (property.key.startsWith("doorbellButtonTrigger")) { + deviceName = property.key.replace("doorbellButtonTrigger","") + if (!getChildDevice("ring:" + deviceName)) { + app.removeSetting(property.key) + } + } + else if (property.key.startsWith("cameraMotionTrigger")) { + logDebug "checking for ${property.key}" + deviceName = property.key.replace("cameraMotionTrigger","") + if (!getChildDevice("ring:" + deviceName)) { + logDebug "deleting it" + app.removeSetting(property.key) + } + } + } +} + +def login() +{ + state.token = null + + def s = "${ringUsername}:${ringPassword}" + logDebug "login()" + + String encodedUandP = s.bytes.encodeBase64() + + def token = "EMPTY" + def params = [ + uri: "https://oauth.ring.com", + path: "/oauth/token", + headers: [ + "User-Agent": "iOS" + ], + requestContentType: "application/json", + body: "{\"client_id\": \"ring_official_ios\",\"grant_type\": \"password\",\"password\": \"${ringPassword}\",\"scope\": \"client\",\"username\": \"${ringUsername}\"}" + ] + try { + httpPost(params) { resp -> + token = resp.data.access_token + } + } catch (e) { + if (e.statusCode == 401) + return false + log.error "HTTP Exception Received on POST: $e" + return + } + + state.token = token + return token +} + + +def getAuthToken(token) +{ + logDebug "getAuthToken()" + params = [ + uri: "https://api.ring.com", + path: "/clients_api/session", + headers: [ + Authorization: "Bearer ${token}", + "User-Agent": "iOS" + ], + requestContentType: "application/x-www-form-urlencoded", + body: "device%5Bos%5D=ios&device%5Bhardware_id%5D=a565187537a28e5cc26819e594e28213&api_version=9" + ] + + def authToken = "EMPTY" + try { + httpPost(params) { resp -> + authToken = resp.data.profile.authentication_token + } + } catch (e) { + if (e.statusCode == 401) + login() + else + log.error "HTTP Exception Received on POST: $e" + + return + } + + return authToken +} + + +def handleOn(device, cameraId) { + logDebug "Handling On event for ${cameraId}" + + runCommandWithRetry(cameraId, "floodlight_light_on") + pause(250) + runCommandWithRetry(cameraId, "floodlight_light_on") + + device.sendEvent(name: "switch", value: "on") +} + +def handleOff(device, cameraId) { + logDebug "Handling Off event for ${cameraId}" + device.updateDataValue("strobing", "false") + logDebug device.getDataValue("strobing") + runCommandWithRetry(cameraId, "floodlight_light_off") + runCommandWithRetry(cameraId, "siren_off") + + device.sendEvent(name: "switch", value: "off") +} + +def handleSiren(device, cameraId) { + logDebug "Handling Siren event for ${cameraId}" + runCommandWithRetry(cameraId, "siren_on", "PUT", [duration: 10]) + device.sendEvent(name: "alarm", value: "siren") +} + +def handleBoth(device, cameraId) { + runCommandWithRetry(cameraId, "siren_on", "PUT", [duration: 10]) + device.sendEvent(name: "alarm", value: "siren") +} + +def handleStrobe(device, cameraId) { +/* def strobePauseInMs = 3000 + def strobeCount = 5 + device.updateDataValue("strobing", "true") + logDebug "Handling Strobe event for ${cameraId}" + device.sendEvent(name: "alarm", value: "strobe") + + for (def i = 0; i < strobeCount; i++) { + logDebug device.getDataValue("strobing") + if (device.getDataValue("strobing") == "false") + return + runCommandWithRetry(cameraId, "floodlight_light_on") + if (device.getDataValue("strobing") == "false") + return + runCommandWithRetry(cameraId, "floodlight_light_on") + if (device.getDataValue("strobing") == "false") + return + pause(strobePauseInMs) + if (device.getDataValue("strobing") == "false") + return + runCommandWithRetry(cameraId, "floodlight_light_off") + if (device.getDataValue("strobing") == "false") + return + pause(strobePauseInMs) + } + device.updateDataValue("strobing", "false")*/ + runCommandWithRetry(cameraId, "siren_on", "PUT", [duration: 10]) + device.sendEvent(name: "alarm", value: "siren") +} + +def handleRefresh() { + updateDevices() +} + +def handleRecord(device, cameraId) { + runCommandWithRetry(cameraId, "vod", "POST") +} + +def runCommand(deviceId, command, method = "PUT", parameters = null) { + def authToken = getAuthToken(state.token) + if (!authToken) return false + + def params = [ + uri: "https://api.ring.com", + path: "/clients_api/doorbots/${deviceId}/${command}", + headers: [ + "User-Agent": "iOS" + ], + query: [ + api_version: "10", + "auth_token": authToken + ] + ] + if (parameters != null) { + map.each { key, value -> + params.query[key] = value + } + } + logDebug "/clients_api/doorbots/${deviceId}/${command}" + def result = null + if (method == "PUT") + { + httpPut(params) { resp -> + result = resp.data + } + } + else if (method == "POST") + { + httpPost(params) { resp -> + result = resp.data + } + } + return result +} + +def runCommandWithRetry(deviceId, command, method = "PUT", parameters = null) { + try + { + return runCommand(deviceId, command, method, parameters) + } + catch (e) + { + if (e.statusCode == 401) + { + login() + return runCommand(deviceId, command, method, parameters) + } + else if (e.statusCode >= 200 && e.statusCode <= 299) + return + else + logDebug e + } +} + +def trigger(level) { + if (level == 0) + return + + def allProperties = this.settings + def deviceName = null + def device = null + for (property in allProperties) { + if (property.key.startsWith("doorbellMotionTrigger")) { + if (this.getProperty(property.key) == level) { + deviceName = property.key.replace("doorbellMotionTrigger","") + device = getChildDevice("ring:" + deviceName) + if (device == null) + continue + logDebug "Triggering motion for ${device}" + device.sendEvent(name: "motion", value: "active") + runIn(5, inactivate, [overwrite: false, data: [device: deviceName]]) + break + } + } + else if (property.key.startsWith("doorbellButtonTrigger")) { + if (this.getProperty(property.key) == level) { + deviceName = property.key.replace("doorbellButtonTrigger","") + device = getChildDevice("ring:" + deviceName) + if (device == null) + continue + logDebug "Triggering button press for ${device}" + device.sendEvent(name: "pushed", value: 1, isStateChange: true) + break + } + } + else if (property.key.startsWith("cameraMotionTrigger")) { + if (this.getProperty(property.key) == level) { + deviceName = property.key.replace("cameraMotionTrigger","") + device = getChildDevice("ring:" + deviceName) + if (device == null) + continue + logDebug "Triggering motion for ${device}" + device.sendEvent(name: "motion", value: "active") + runIn(5, inactivate, [overwrite: false, data: [device: deviceName]]) + break + } + } + } +} + +def inactivate(data) { + + def device = getChildDevice("ring:" + data.device) + logDebug "Cancelling motion for ${device}" + device.sendEvent(name:"motion", value: "inactive") +} + +private logDebug(msg) { + if (settings?.debugOutput) { + log.debug msg + } +} diff --git a/drivers/Ring_Battery_Camera.groovy b/drivers/Ring_Battery_Camera.groovy new file mode 100644 index 0000000..f28c543 --- /dev/null +++ b/drivers/Ring_Battery_Camera.groovy @@ -0,0 +1,45 @@ +/** + * Ring Battery Camera + * + * Copyright 2019 Dominick Meglio + * + */ +metadata { + definition (name: "Ring Battery Camera", namespace: "ring", author: "Dominick Meglio") { + capability "Light" + capability "Refresh" + capability "Motion Sensor" + capability "Battery" + capability "Alarm" + + command "record" + } +} + +def on() { + parent.handleOn(device, device.deviceNetworkId.split(":")[1]) +} + +def off() { + parent.handleOff(device, device.deviceNetworkId.split(":")[1]) +} + +def siren() { + parent.handleSiren(device, device.deviceNetworkId.split(":")[1]) +} + +def strobe() { + parent.handleStrobe(device, device.deviceNetworkId.split(":")[1]) +} + +def record() { + parent.handleRecord(device, device.deviceNetworkId.split(":")[1]) +} + +def both() { + parent.handleBoth(device, device.deviceNetworkId.split(":")[1]) +} + +def refresh() { + parent.handleRefresh() +} diff --git a/drivers/Ring_Camera.groovy b/drivers/Ring_Camera.groovy new file mode 100644 index 0000000..de03a3f --- /dev/null +++ b/drivers/Ring_Camera.groovy @@ -0,0 +1,44 @@ +/** + * Ring Camera + * + * Copyright 2019 Dominick Meglio + * + */ +metadata { + definition (name: "Ring Camera", namespace: "ring", author: "Dominick Meglio") { + capability "Switch" + capability "Refresh" + capability "Motion Sensor" + capability "Alarm" + + command "record" + } +} + +def on() { + parent.handleOn(device, device.deviceNetworkId.split(":")[1]) +} + +def off() { + parent.handleOff(device, device.deviceNetworkId.split(":")[1]) +} + +def siren() { + parent.handleSiren(device, device.deviceNetworkId.split(":")[1]) +} + +def strobe() { + parent.handleStrobe(device, device.deviceNetworkId.split(":")[1]) +} + +def both() { + parent.handleBoth(device, device.deviceNetworkId.split(":")[1]) +} + +def refresh() { + parent.handleRefresh() +} + +def record() { + parent.handleRecord(device, device.deviceNetworkId.split(":")[1]) +} diff --git a/drivers/Ring_Doorbell.groovy b/drivers/Ring_Doorbell.groovy new file mode 100644 index 0000000..be354a3 --- /dev/null +++ b/drivers/Ring_Doorbell.groovy @@ -0,0 +1,27 @@ +/** + * Ring Doorbell + * + * Copyright 2019 Dominick Meglio + * + */ +metadata { + definition (name: "Ring Doorbell", namespace: "ring", author: "Dominick Meglio") { + capability "Momentary" + capability "Motion Sensor" + capability "PushableButton" + + command "record" + } +} + +def record() { + parent.handleRecord(device, device.deviceNetworkId.split(":")[1]) +} + +def installed() { + sendEvent(name: "numberOfButtons", value: "1") +} + +def push() { + sendEvent(name: "pushed", value: 1, stateChange: true) +} diff --git a/drivers/Ring_Manager.groovy b/drivers/Ring_Manager.groovy new file mode 100644 index 0000000..f9a14a2 --- /dev/null +++ b/drivers/Ring_Manager.groovy @@ -0,0 +1,17 @@ +/** + * Ring Manager + * + * Copyright 2019 Dominick Meglio + * + */ +metadata { + definition (name: "Ring Manager", namespace: "ring", author: "Dominick Meglio") { + capability "SwitchLevel" + } + +} + +def setLevel(level) { + parent.trigger(level) + sendEvent(name: "switchLevel", value: "0") +}