Compare commits
20 Commits
user186_2
...
MSA-2517-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1e93a4862 | ||
|
|
341943f342 | ||
|
|
ecd7bf2a82 | ||
|
|
ee3f125bca | ||
|
|
c93435b14d | ||
|
|
134b108df5 | ||
|
|
b850e772d7 | ||
|
|
0d617ab5a8 | ||
|
|
e8ef2d052f | ||
|
|
001ba48b5c | ||
|
|
1e088b1655 | ||
|
|
6a91660719 | ||
|
|
b007d59220 | ||
|
|
5043ccef9a | ||
|
|
2129c6c5d3 | ||
|
|
fd9d4fe283 | ||
|
|
a241804190 | ||
|
|
22de1551fe | ||
|
|
2bcf1de354 | ||
|
|
92d6d13f62 |
974
devicetypes/brbeaird/myq-lite.src/myq-lite.groovy
Normal file
974
devicetypes/brbeaird/myq-lite.src/myq-lite.groovy
Normal file
@@ -0,0 +1,974 @@
|
||||
/**
|
||||
* MyQ Lite
|
||||
*
|
||||
* Copyright 2017 Jason Mok/Brian Beaird/Barry Burke
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Last Updated : 11/28/2017
|
||||
* SmartApp version: 2.0.3*
|
||||
* Door device version: 2.1.2*
|
||||
* Door-no-sensor device version: 1.1.1*
|
||||
* Light device version: 1.0.1*
|
||||
*/
|
||||
include 'asynchttp_v1'
|
||||
|
||||
definition(
|
||||
name: "MyQ Lite",
|
||||
namespace: "brbeaird",
|
||||
author: "Jason Mok/Brian Beaird/Barry Burke",
|
||||
description: "Integrate MyQ with Smartthings",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://raw.githubusercontent.com/brbeaird/SmartThings_MyQ/master/icons/myq.png",
|
||||
iconX2Url: "https://raw.githubusercontent.com/brbeaird/SmartThings_MyQ/master/icons/myq@2x.png",
|
||||
iconX3Url: "https://raw.githubusercontent.com/brbeaird/SmartThings_MyQ/master/icons/myq@3x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
page(name: "prefLogIn", title: "MyQ")
|
||||
page(name: "prefListDevices", title: "MyQ")
|
||||
page(name: "prefSensor1", title: "MyQ")
|
||||
page(name: "prefSensor2", title: "MyQ")
|
||||
page(name: "prefSensor3", title: "MyQ")
|
||||
page(name: "prefSensor4", title: "MyQ")
|
||||
page(name: "summary", title: "MyQ")
|
||||
}
|
||||
|
||||
|
||||
/* Preferences */
|
||||
def prefLogIn() {
|
||||
state.previousVersion = state.thisSmartAppVersion
|
||||
if (state.previousVersion == null){
|
||||
state.previousVersion = 0;
|
||||
}
|
||||
state.thisSmartAppVersion = "2.0.3"
|
||||
state.installMsg = ""
|
||||
def showUninstall = username != null && password != null
|
||||
return dynamicPage(name: "prefLogIn", title: "Connect to MyQ", nextPage:"prefListDevices", uninstall:showUninstall, install: false) {
|
||||
section("Login Credentials"){
|
||||
input("username", "email", title: "Username", description: "MyQ Username (email address)")
|
||||
input("password", "password", title: "Password", description: "MyQ password")
|
||||
}
|
||||
section("Gateway Brand"){
|
||||
input(name: "brand", title: "Gateway Brand", type: "enum", metadata:[values:["Liftmaster","Chamberlain","Craftsman"]] )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def prefListDevices() {
|
||||
getVersionInfo(0, 0);
|
||||
getSelectedDevices("lights")
|
||||
if (forceLogin()) {
|
||||
def doorList = getDoorList()
|
||||
if ((state.doorList) || (state.lightList)){
|
||||
def nextPage = "prefSensor1"
|
||||
if (!state.doorList){nextPage = "summary"} //Skip to summary if there are no doors to handle
|
||||
return dynamicPage(name: "prefListDevices", title: "Devices", nextPage:nextPage, install:false, uninstall:true) {
|
||||
if (state.doorList) {
|
||||
section("Select which garage door/gate to use"){
|
||||
input(name: "doors", type: "enum", required:false, multiple:true, metadata:[values:state.doorList])
|
||||
}
|
||||
}
|
||||
if (state.lightList) {
|
||||
section("Select which lights to use"){
|
||||
input(name: "lights", type: "enum", required:false, multiple:true, metadata:[values:state.lightList])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}else {
|
||||
def devList = getDeviceList()
|
||||
return dynamicPage(name: "prefListDevices", title: "Error!", install:false, uninstall:true) {
|
||||
section(""){
|
||||
paragraph "Could not find any supported device(s). Please report to author about these devices: " + devList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
return dynamicPage(name: "prefListDevices", title: "Error!", install:false, uninstall:true) {
|
||||
section(""){
|
||||
paragraph "The username or password you entered is incorrect. Try again. "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def prefSensor1() {
|
||||
log.debug "Doors chosen: " + doors
|
||||
|
||||
//Set defaults
|
||||
def nextPage = "summary"
|
||||
def titleText = ""
|
||||
|
||||
//Determine if we have multiple doors and need to send to another page
|
||||
if (doors instanceof String){ //simulator seems to just make a single door a string. For that reason we have this weird check.
|
||||
log.debug "Single door detected (string)."
|
||||
titleText = "Select Sensors for Door 1 (" + state.data[doors].name + ")"
|
||||
}
|
||||
else if (doors.size() == 1){
|
||||
log.debug "Single door detected (array)."
|
||||
titleText = "Select Sensors for Door 1 (" + state.data[doors[0]].name + ")"
|
||||
}
|
||||
else{
|
||||
log.debug "Multiple doors detected."
|
||||
nextPage = "prefSensor2"
|
||||
titleText = "OPTIONAL: Select Sensors for Door 1 (" + state.data[doors[0]].name + ")"
|
||||
}
|
||||
|
||||
return dynamicPage(name: "prefSensor1", title: "Optional Sensors and Push Buttons", nextPage:nextPage, install:false, uninstall:true) {
|
||||
section(titleText){
|
||||
paragraph "Optional: If you have sensors on this door, select them below. A sensor allows the device type to know whether the door is open or closed, which helps the device function " +
|
||||
"as a switch you can turn on (to open) and off (to close)."
|
||||
input(name: "door1Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: false, multiple: false)
|
||||
input(name: "door1Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
|
||||
}
|
||||
section("Create separate on/off push buttons?"){
|
||||
paragraph "Choose the option below to have separate additional On and Off push button devices created. This is recommened if you have no sensors but still want a way to open/close the " +
|
||||
"garage from SmartTiles and other interfaces like Google Home that can't function with the built-in open/close capability. See wiki for more details."
|
||||
input "prefDoor1PushButtons", "bool", required: false, title: "Create on/off push buttons?"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def prefSensor2() {
|
||||
def nextPage = "summary"
|
||||
def titleText = "Sensors for Door 2 (" + state.data[doors[1]].name + ")"
|
||||
|
||||
if (doors.size() > 2){
|
||||
nextPage = "prefSensor3"
|
||||
}
|
||||
|
||||
return dynamicPage(name: "prefSensor2", title: "Optional Sensors and Push Buttons", nextPage:nextPage, install:false, uninstall:true) {
|
||||
section(titleText){
|
||||
input(name: "door2Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: false, multiple: false)
|
||||
input(name: "door2Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
|
||||
}
|
||||
section("Create separate on/off push buttons?"){
|
||||
paragraph "Choose the option below to have extra on and off push button devices created. This is recommened if you have no sensors but still want a way to open/close the garage from SmartTiles."
|
||||
input "prefDoor2PushButtons", "bool", required: false, title: "Create on/off push buttons?"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def prefSensor3() {
|
||||
def nextPage = "summary"
|
||||
def titleText = "Sensors for Door 3 (" + state.data[doors[2]].name + ")"
|
||||
|
||||
if (doors.size() > 3){
|
||||
nextPage = "prefSensor4"
|
||||
}
|
||||
|
||||
return dynamicPage(name: "prefSensor3", title: "Optional Sensors and Push Buttons", nextPage:nextPage, install:false, uninstall:true) {
|
||||
section(titleText){
|
||||
input(name: "door3Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: false, multiple: false)
|
||||
input(name: "door3Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
|
||||
}
|
||||
section("Create separate on/off push buttons?"){
|
||||
paragraph "Choose the option below to have extra on and off push button devices created. This is recommened if you have no sensors but still want a way to open/close the garage from SmartTiles."
|
||||
input "prefDoor3PushButtons", "bool", required: false, title: "Create on/off push buttons?"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def prefSensor4() {
|
||||
def titleText = "Contact Sensor for Door 4 (" + state.data[doors[3]].name + ")"
|
||||
return dynamicPage(name: "prefSensor4", title: "Optional Sensors and Push Buttons", nextPage:"summary", install:false, uninstall:true) {
|
||||
section(titleText){
|
||||
input(name: "door4Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: "false", multiple: "false")
|
||||
input(name: "door4Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
|
||||
}
|
||||
section("Create separate on/off push buttons?"){
|
||||
paragraph "Choose the option below to have extra on and off push button devices created. This is recommened if you have no sensors but still want a way to open/close the garage from SmartTiles."
|
||||
input "prefDoor4PushButtons", "bool", required: false, title: "Create on/off push buttons?"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def summary() {
|
||||
state.installMsg = ""
|
||||
initialize()
|
||||
versionCheck()
|
||||
return dynamicPage(name: "summary", title: "Summary", install:true, uninstall:true) {
|
||||
section("Installation Details:"){
|
||||
paragraph state.installMsg
|
||||
paragraph state.versionWarning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialization */
|
||||
def installed() {
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated..."
|
||||
if (state.previousVersion != state.thisSmartAppVersion){
|
||||
getVersionInfo(state.previousVersion, state.thisSmartAppVersion);
|
||||
}
|
||||
}
|
||||
|
||||
def uninstalled() {
|
||||
getVersionInfo(state.previousVersion, 0);
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
unsubscribe()
|
||||
log.debug "Initializing..."
|
||||
login()
|
||||
state.sensorMap = [:]
|
||||
|
||||
// Get initial device status in state.data
|
||||
state.polling = [ last: 0, rescheduler: now() ]
|
||||
state.data = [:]
|
||||
|
||||
// Create selected devices
|
||||
def doorsList = getDoorList()
|
||||
def lightsList = state.lightList
|
||||
|
||||
if (doors != null){
|
||||
def firstDoor = doors[0]
|
||||
//Handle single door (sometimes it's just a dumb string thanks to the simulator)
|
||||
if (doors instanceof String)
|
||||
firstDoor = doors
|
||||
|
||||
//Create door devices
|
||||
createChilDevices(firstDoor, door1Sensor, doorsList[firstDoor], prefDoor1PushButtons)
|
||||
if (doors[1]) createChilDevices(doors[1], door2Sensor, doorsList[doors[1]], prefDoor2PushButtons)
|
||||
if (doors[2]) createChilDevices(doors[2], door3Sensor, doorsList[doors[2]], prefDoor3PushButtons)
|
||||
if (doors[3]) createChilDevices(doors[3], door4Sensor, doorsList[doors[3]], prefDoor4PushButtons)
|
||||
}
|
||||
|
||||
//Create light devices
|
||||
def selectedLights = getSelectedDevices("lights")
|
||||
selectedLights.each {
|
||||
log.debug "Checking for existing light: " + it
|
||||
if (!getChildDevice(it)) {
|
||||
log.debug "Creating child light device: " + it
|
||||
|
||||
try{
|
||||
addChildDevice("brbeaird", "MyQ Light Controller", it, getHubID(), ["name": lightsList[it]])
|
||||
state.installMsg = state.installMsg + lightsList[it] + ": created light device. \r\n\r\n"
|
||||
}
|
||||
catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
|
||||
{
|
||||
log.debug "Error! " + e
|
||||
state.installMsg = state.installMsg + lightsList[it] + ": problem creating light device. Check your IDE to make sure the brbeaird : MyQ Light Controller device handler is installed and published. \r\n\r\n"
|
||||
}
|
||||
}
|
||||
else{
|
||||
log.debug "Light device already exists: " + it
|
||||
state.installMsg = state.installMsg + lightsList[it] + ": light device already exists. \r\n\r\n"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Remove unselected devices
|
||||
def selectedDevices = [] + getSelectedDevices("doors") + getSelectedDevices("lights")
|
||||
getChildDevices().each{
|
||||
//Modify DNI string for the extra pushbuttons to make sure they don't get deleted unintentionally
|
||||
def DNI = it?.deviceNetworkId
|
||||
DNI = DNI.replace(" Opener", "")
|
||||
DNI = DNI.replace(" Closer", "")
|
||||
|
||||
if (!(DNI in selectedDevices)){
|
||||
log.debug "found device to delete: " + it
|
||||
try{
|
||||
deleteChildDevice(it.deviceNetworkId)
|
||||
} catch (e){
|
||||
sendPush("Warning: unable to delete door or button - " + it + "- you'll need to manually remove it.")
|
||||
log.debug "Error trying to delete device " + it + " - " + e
|
||||
log.debug "Device is likely in use in a Routine, or SmartApp (make sure and check SmarTiles!)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Create subscriptions
|
||||
if (door1Sensor)
|
||||
subscribe(door1Sensor, "contact", sensorHandler)
|
||||
if (door2Sensor)
|
||||
subscribe(door2Sensor, "contact", sensorHandler)
|
||||
if (door3Sensor)
|
||||
subscribe(door3Sensor, "contact", sensorHandler)
|
||||
if (door4Sensor)
|
||||
subscribe(door4Sensor, "contact", sensorHandler)
|
||||
|
||||
if (door1Acceleration)
|
||||
subscribe(door1Acceleration, "acceleration", sensorHandler)
|
||||
if (door2Acceleration)
|
||||
subscribe(door2Acceleration, "acceleration", sensorHandler)
|
||||
if (door3Acceleration)
|
||||
subscribe(door3Acceleration, "acceleration", sensorHandler)
|
||||
if (door4Acceleration)
|
||||
subscribe(door4Acceleration, "acceleration", sensorHandler)
|
||||
|
||||
//Set initial values
|
||||
if (door1Sensor)
|
||||
syncDoorsWithSensors()
|
||||
}
|
||||
|
||||
def createChilDevices(door, sensor, doorName, prefPushButtons){
|
||||
log.debug "In CreateChild"
|
||||
if (door){
|
||||
//Has door's child device already been created?
|
||||
def existingDev = getChildDevice(door)
|
||||
def existingType = existingDev?.typeName
|
||||
|
||||
if (existingDev){
|
||||
log.debug "Child already exists for " + doorName + ". Sensor name is: " + sensor
|
||||
state.installMsg = state.installMsg + doorName + ": door device already exists. \r\n\r\n"
|
||||
if ((!sensor) && existingType == "MyQ Garage Door Opener"){
|
||||
log.debug "Type needs updating to non-sensor version"
|
||||
existingDev.deviceType = "MyQ Garage Door Opener-NoSensor2"
|
||||
state.installMsg = state.installMsg + doorName + ": changed door device to No-sensor version." + "\r\n\r\n"
|
||||
}
|
||||
|
||||
if (sensor && existingType == "MyQ Garage Door Opener-NoSensor"){
|
||||
log.debug "Type needs updating to sensor version"
|
||||
existingDev.deviceType = "MyQ Garage Door Opener"
|
||||
state.installMsg = state.installMsg + doorName + ": changed door device to sensor version." + "\r\n\r\n"
|
||||
}
|
||||
}
|
||||
else{
|
||||
log.debug "Creating child door device " + door
|
||||
|
||||
if (sensor){
|
||||
try{
|
||||
log.debug "Creating door with sensor"
|
||||
addChildDevice("brbeaird", "MyQ Garage Door Opener", door, getHubID(), ["name": doorName])
|
||||
state.installMsg = state.installMsg + doorName + ": created door device (sensor version) \r\n\r\n"
|
||||
}
|
||||
catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
|
||||
{
|
||||
log.debug "Error! " + e
|
||||
state.installMsg = state.installMsg + doorName + ": problem creating door device (sensor type). Check your IDE to make sure the brbeaird : MyQ Garage Door Opener device handler is installed and published. \r\n\r\n"
|
||||
|
||||
}
|
||||
}
|
||||
else{
|
||||
try{
|
||||
log.debug "Creating door with no sensor"
|
||||
addChildDevice("brbeaird", "MyQ Garage Door Opener-NoSensor", door, getHubID(), ["name": doorName])
|
||||
state.installMsg = state.installMsg + doorName + ": created door device (no-sensor version) \r\n\r\n"
|
||||
}
|
||||
catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
|
||||
{
|
||||
log.debug "Error! " + e
|
||||
state.installMsg = state.installMsg + doorName + ": problem creating door device (no-sensor type). Check your IDE to make sure the brbeaird : MyQ Garage Door Opener-NoSensor device handler is installed and published. \r\n\r\n"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Create push button devices
|
||||
if (prefPushButtons){
|
||||
def existingOpenButtonDev = getChildDevice(door + " Opener")
|
||||
def existingCloseButtonDev = getChildDevice(door + " Closer")
|
||||
if (!existingOpenButtonDev){
|
||||
try{
|
||||
def openButton = addChildDevice("smartthings", "Momentary Button Tile", door + " Opener", getHubID(), [name: doorName + " Opener", label: doorName + " Opener"])
|
||||
state.installMsg = state.installMsg + doorName + ": created push button device. \r\n\r\n"
|
||||
subscribe(openButton, "momentary.pushed", doorButtonOpenHandler)
|
||||
}
|
||||
catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
|
||||
{
|
||||
log.debug "Error! " + e
|
||||
state.installMsg = state.installMsg + doorName + ": problem creating push button device. Check your IDE to make sure the smartthings : Momentary Button Tile device handler is installed and published. \r\n\r\n"
|
||||
}
|
||||
}
|
||||
else{
|
||||
subscribe(existingOpenButtonDev, "momentary.pushed", doorButtonOpenHandler)
|
||||
state.installMsg = state.installMsg + doorName + ": push button device already exists. Subscription recreated. \r\n\r\n"
|
||||
log.debug "subscribed to button: " + existingOpenButtonDev
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (!existingCloseButtonDev){
|
||||
try{
|
||||
def closeButton = addChildDevice("smartthings", "Momentary Button Tile", door + " Closer", getHubID(), [name: doorName + " Closer", label: doorName + " Closer"])
|
||||
subscribe(closeButton, "momentary.pushed", doorButtonCloseHandler)
|
||||
}
|
||||
catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
|
||||
{
|
||||
log.debug "Error! " + e
|
||||
}
|
||||
}
|
||||
else{
|
||||
subscribe(existingCloseButtonDev, "momentary.pushed", doorButtonCloseHandler)
|
||||
}
|
||||
}
|
||||
|
||||
//Cleanup defunct push button devices if no longer wanted
|
||||
else{
|
||||
def pushButtonIDs = [door + " Opener", door + " Closer"]
|
||||
log.debug "ID's to look for: " + pushButtonIDs
|
||||
def devsToDelete = getChildDevices().findAll { pushButtonIDs.contains(it.deviceNetworkId)}
|
||||
log.debug "button devices to delete: " + devsToDelete
|
||||
devsToDelete.each{
|
||||
log.debug "deleting button: " + it
|
||||
try{
|
||||
deleteChildDevice(it.deviceNetworkId)
|
||||
} catch (e){
|
||||
//sendPush("Warning: unable to delete virtual on/off push button - you'll need to manually remove it.")
|
||||
state.installMsg = state.installMsg + "Warning: unable to delete virtual on/off push button - you'll need to manually remove it. \r\n\r\n"
|
||||
log.debug "Error trying to delete button " + it + " - " + e
|
||||
log.debug "Button is likely in use in a Routine, or SmartApp (make sure and check SmarTiles!)."
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def syncDoorsWithSensors(child){
|
||||
def firstDoor = doors[0]
|
||||
|
||||
//Handle single door (sometimes it's just a dumb string thanks to the simulator)
|
||||
if (doors instanceof String)
|
||||
firstDoor = doors
|
||||
|
||||
def doorDNI = null
|
||||
if (child) { // refresh only the requesting door (makes things a bit more efficient if you have more than 1 door
|
||||
doorDNI = child.device.deviceNetworkId
|
||||
switch (doorDNI) {
|
||||
case firstDoor:
|
||||
updateDoorStatus(firstDoor, door1Sensor, door1Acceleration, door1ThreeAxis, child)
|
||||
break
|
||||
case doors[1]:
|
||||
updateDoorStatus(doors[1], door2Sensor, door2Acceleration, door2ThreeAxis, child)
|
||||
break
|
||||
case doors[2]:
|
||||
updateDoorStatus(doors[2], door3Sensor, door3Acceleration, door3ThreeAxis, child)
|
||||
break
|
||||
case doors[3]:
|
||||
updateDoorStatus(doors[3], door4Sensor, door4Acceleration, door4ThreeAxis, child)
|
||||
}
|
||||
} else { // refresh ALL the doors
|
||||
if (firstDoor) updateDoorStatus(firstDoor, door1Sensor, door1Acceleration, door1ThreeAxis, null)
|
||||
if (doors[1]) updateDoorStatus(doors[1], door2Sensor, door2Acceleration, door2ThreeAxis, null)
|
||||
if (doors[2]) updateDoorStatus(doors[2], door3Sensor, door3Acceleration, door3ThreeAxis, null)
|
||||
if (doors[3]) updateDoorStatus(doors[3], door4Sensor, door4Acceleration, door4ThreeAxis, null)
|
||||
}
|
||||
}
|
||||
|
||||
def updateDoorStatus(doorDNI, sensor, acceleration, threeAxis, child){
|
||||
|
||||
//Get door to update and set the new value
|
||||
def doorToUpdate = getChildDevice(doorDNI)
|
||||
def doorName = state.data[doorDNI].name
|
||||
|
||||
def value = "unknown"
|
||||
def moving = "unknown"
|
||||
def door = doorToUpdate.latestValue("door")
|
||||
|
||||
if (acceleration) moving = acceleration.latestValue("acceleration")
|
||||
if (sensor) value = sensor.latestValue("contact")
|
||||
|
||||
if (moving == "active") {
|
||||
if (value == "open") {
|
||||
if (door != "opening") value = "closing" else value = "opening" // if door is "open" or "waiting" change to "closing", else it must be "opening"
|
||||
} else if (value == "closed") {
|
||||
if (door != "closing") value = "opening" else value = "closed"
|
||||
}
|
||||
} else if (moving == "inactive") {
|
||||
if (door == "closing") {
|
||||
if (value == "open") { // just stopped but door is still open
|
||||
value = "stopped"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doorToUpdate.updateDeviceStatus(value)
|
||||
doorToUpdate.updateDeviceSensor("${sensor} is ${sensor?.currentContact}")
|
||||
|
||||
log.debug "Door: " + doorName + ": Updating with status - " + value + " - from sensor " + sensor
|
||||
|
||||
//Write to child log if this was initiated from one of the doors
|
||||
if (child)
|
||||
child.log("Door: " + doorName + ": Updating with status - " + value + " - from sensor " + sensor)
|
||||
|
||||
if (acceleration) {
|
||||
doorToUpdate.updateDeviceMoving("${acceleration} is ${moving}")
|
||||
log.debug "Door: " + doorName + ": Updating with status - " + moving + " - from sensor " + acceleration
|
||||
if (child)
|
||||
child.log("Door: " + doorName + ": Updating with status - " + moving + " - from sensor " + acceleration)
|
||||
}
|
||||
|
||||
//Get latest activity timestamp for the sensor (data saved for up to a week)
|
||||
def eventsSinceYesterday = sensor.eventsSince(new Date() - 7)
|
||||
def latestEvent = eventsSinceYesterday[0]?.date
|
||||
def timeStampLogText = "Door: " + doorName + ": Updating timestamp to: " + latestEvent + " - from sensor " + sensor
|
||||
|
||||
if (!latestEvent) //If the door has been inactive for more than a week, timestamp data will be null. Keep current value in that case.
|
||||
timeStampLogText = "Door: " + doorName + ": Null timestamp detected " + " - from sensor " + sensor + " . Keeping current value."
|
||||
else
|
||||
doorToUpdate.updateDeviceLastActivity(latestEvent)
|
||||
|
||||
log.debug timeStampLogText
|
||||
|
||||
//Write to child log if this was initiated from one of the doors
|
||||
if (child)
|
||||
child.log(timeStampLogText)
|
||||
}
|
||||
|
||||
def refresh(child){
|
||||
def door = child.device.deviceNetworkId
|
||||
def doorName = state.data[door].name
|
||||
child.log("refresh called from " + doorName + ' (' + door + ')')
|
||||
syncDoorsWithSensors(child)
|
||||
}
|
||||
|
||||
def sensorHandler(evt) {
|
||||
log.debug "Sensor change detected: Event name " + evt.name + " value: " + evt.value + " deviceID: " + evt.deviceId
|
||||
|
||||
//If we're seeing vibration sensor values, ignore them if it's been more than 30 seconds after a command was sent.
|
||||
// This keeps us from seeing phantom entries from overly-sensitive sensors
|
||||
if (evt.value == "active" || evt.value == "inactive"){
|
||||
if (state.lastCommandSent == null || state.lastCommandSent > now()-30000){
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
switch (evt.deviceId) {
|
||||
case door1Sensor.id:
|
||||
case door1Acceleration?.id:
|
||||
def firstDoor = doors[0]
|
||||
if (doors instanceof String) firstDoor = doors
|
||||
updateDoorStatus(firstDoor, door1Sensor, door1Acceleration, door1ThreeAxis, null)
|
||||
break
|
||||
case door2Sensor?.id:
|
||||
case door2Acceleration?.id:
|
||||
updateDoorStatus(doors[1], door2Sensor, door2Acceleration, door2ThreeAxis, null)
|
||||
break
|
||||
case door3Sensor?.id:
|
||||
case door3Acceleration?.id:
|
||||
updateDoorStatus(doors[2], door3Sensor, door3Acceleration, door3ThreeAxis, null)
|
||||
break
|
||||
case door4Sensor?.id:
|
||||
case door4Acceleration?.id:
|
||||
updateDoorStatus(doors[3], door4Sensor, door4Acceleration, door4ThreeAxis, null)
|
||||
break
|
||||
default:
|
||||
syncDoorsWithSensors()
|
||||
}
|
||||
}
|
||||
|
||||
def doorButtonOpenHandler(evt) {
|
||||
log.debug "Door open button push detected: Event name " + evt.name + " value: " + evt.value + " deviceID: " + evt.deviceId + " DNI: " + evt.getDevice().deviceNetworkId
|
||||
def doorDeviceDNI = evt.getDevice().deviceNetworkId
|
||||
doorDeviceDNI = doorDeviceDNI.replace(" Opener", "")
|
||||
def doorDevice = getChildDevice(doorDeviceDNI)
|
||||
log.debug "Opening door."
|
||||
doorDevice.openPrep()
|
||||
sendCommand(doorDevice, "desireddoorstate", 1)
|
||||
}
|
||||
|
||||
def doorButtonCloseHandler(evt) {
|
||||
log.debug "Door close button push detected: Event name " + evt.name + " value: " + evt.value + " deviceID: " + evt.deviceId + " DNI: " + evt.getDevice().deviceNetworkId
|
||||
def doorDeviceDNI = evt.getDevice().deviceNetworkId
|
||||
doorDeviceDNI = doorDeviceDNI.replace(" Closer", "")
|
||||
def doorDevice = getChildDevice(doorDeviceDNI)
|
||||
log.debug "Closing door."
|
||||
doorDevice.closePrep()
|
||||
sendCommand(doorDevice, "desireddoorstate", 0)
|
||||
}
|
||||
|
||||
|
||||
def getSelectedDevices( settingsName ) {
|
||||
def selectedDevices = []
|
||||
(!settings.get(settingsName))?:((settings.get(settingsName)?.getAt(0)?.size() > 1) ? settings.get(settingsName)?.each { selectedDevices.add(it) } : selectedDevices.add(settings.get(settingsName)))
|
||||
return selectedDevices
|
||||
}
|
||||
|
||||
/* Access Management */
|
||||
private forceLogin() {
|
||||
//Reset token and expiry
|
||||
state.session = [ brandID: 0, brandName: settings.brand, securityToken: null, expiration: 0 ]
|
||||
state.polling = [ last: 0, rescheduler: now() ]
|
||||
state.data = [:]
|
||||
return doLogin()
|
||||
}
|
||||
|
||||
private login() { return (!(state.session.expiration > now())) ? doLogin() : true }
|
||||
|
||||
private doLogin() {
|
||||
apiPostLogin("/api/v4/User/Validate", [ username: settings.username, password: settings.password ]) { response ->
|
||||
log.debug "got login response: " + response
|
||||
if (response.status == 200) {
|
||||
if (response.data.SecurityToken != null) {
|
||||
state.session.brandID = response.data.BrandId
|
||||
state.session.brandName = response.data.BrandName
|
||||
state.session.securityToken = response.data.SecurityToken
|
||||
state.session.expiration = now() + 150000
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listing all the garage doors you have in MyQ
|
||||
private getDoorList() {
|
||||
state.doorList = [:]
|
||||
state.lightList = [:]
|
||||
|
||||
def deviceList = [:]
|
||||
apiGet("/api/v4/userdevicedetails/get", []) { response ->
|
||||
if (response.status == 200) {
|
||||
//log.debug "response data: " + response.data
|
||||
//sendAlert("response data: " + response.data.Devices)
|
||||
response.data.Devices.each { device ->
|
||||
// 2 = garage door, 5 = gate, 7 = MyQGarage(no gateway), 17 = Garage Door Opener WGDO
|
||||
if (device.MyQDeviceTypeId == 2||device.MyQDeviceTypeId == 5||device.MyQDeviceTypeId == 7||device.MyQDeviceTypeId == 17) {
|
||||
log.debug "Found door: " + device.MyQDeviceId
|
||||
def dni = [ app.id, "GarageDoorOpener", device.MyQDeviceId ].join('|')
|
||||
def description = ''
|
||||
def doorState = ''
|
||||
def updatedTime = ''
|
||||
device.Attributes.each {
|
||||
|
||||
if (it.AttributeDisplayName=="desc") //deviceList[dni] = it.Value
|
||||
{
|
||||
description = it.Value
|
||||
}
|
||||
|
||||
if (it.AttributeDisplayName=="doorstate") {
|
||||
doorState = it.Value
|
||||
updatedTime = it.UpdatedTime
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Sometimes MyQ has duplicates. Check and see if we've seen this door before
|
||||
def doorToRemove = ""
|
||||
state.data.each { doorDNI, door ->
|
||||
if (door.name == description){
|
||||
log.debug "Duplicate door detected. Checking to see if this one is newer..."
|
||||
|
||||
//If this instance is newer than the duplicate, pull the older one back out of the array
|
||||
if (door.lastAction < updatedTime){
|
||||
log.debug "Yep, this one is newer."
|
||||
doorToRemove = door
|
||||
}
|
||||
|
||||
//If this door is the older one, clear out the description so it will be ignored
|
||||
else{
|
||||
log.debug "Nope, this one is older. Stick with what we've got."
|
||||
description = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
if (doorToRemove){
|
||||
log.debug "Removing older duplicate."
|
||||
state.data.remove(door)
|
||||
state.doorList.remove(door)
|
||||
}
|
||||
|
||||
//Ignore any doors with blank descriptions
|
||||
if (description != ''){
|
||||
log.debug "Storing door info: " + description + "type: " + device.MyQDeviceTypeId + " status: " + doorState + " type: " + device.MyQDeviceTypeName
|
||||
deviceList[dni] = description
|
||||
state.doorList[dni] = description
|
||||
state.data[dni] = [ status: doorState, lastAction: updatedTime, name: description, type: device.MyQDeviceTypeId ]
|
||||
}
|
||||
else{
|
||||
log.debug "Door " + device.MyQDeviceId + " has blank desc field. This is unusual..."
|
||||
}
|
||||
}
|
||||
|
||||
//Lights!
|
||||
if (device.MyQDeviceTypeId == 3) {
|
||||
log.debug "Found light: " + device.MyQDeviceId
|
||||
def dni = [ app.id, "LightController", device.MyQDeviceId ].join('|')
|
||||
def description = ''
|
||||
def lightState = ''
|
||||
def updatedTime = ''
|
||||
device.Attributes.each {
|
||||
|
||||
if (it.AttributeDisplayName=="desc") //deviceList[dni] = it.Value
|
||||
{
|
||||
description = it.Value
|
||||
}
|
||||
|
||||
if (it.AttributeDisplayName=="lightstate") {
|
||||
lightState = it.Value
|
||||
updatedTime = it.UpdatedTime
|
||||
}
|
||||
}
|
||||
|
||||
//Ignore any lights with blank descriptions
|
||||
if (description != ''){
|
||||
log.debug "Storing light info: " + description + "type: " + device.MyQDeviceTypeId + " status: " + doorState + " type: " + device.MyQDeviceTypeName
|
||||
deviceList[dni] = description
|
||||
state.lightList[dni] = description
|
||||
state.data[dni] = [ status: lightState, lastAction: updatedTime, name: description, type: device.MyQDeviceTypeId ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deviceList
|
||||
}
|
||||
|
||||
private getDeviceList() {
|
||||
def deviceList = []
|
||||
apiGet("/api/v4/userdevicedetails/get", []) { response ->
|
||||
if (response.status == 200) {
|
||||
response.data.Devices.each { device ->
|
||||
log.debug "MyQDeviceTypeId : " + device.MyQDeviceTypeId.toString()
|
||||
if (!(device.MyQDeviceTypeId == 1||device.MyQDeviceTypeId == 2||device.MyQDeviceTypeId == 3||device.MyQDeviceTypeId == 5||device.MyQDeviceTypeId == 7)) {
|
||||
device.Attributes.each {
|
||||
def description = ''
|
||||
def doorState = ''
|
||||
def updatedTime = ''
|
||||
if (it.AttributeDisplayName=="desc") //deviceList[dni] = it.Value
|
||||
description = it.Value
|
||||
|
||||
//Ignore any doors with blank descriptions
|
||||
if (description != ''){
|
||||
log.debug "found device: " + description
|
||||
deviceList.add( device.MyQDeviceTypeId.toString() + "|" + device.TypeID )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deviceList
|
||||
}
|
||||
|
||||
def getHubID(){
|
||||
def hubID
|
||||
def hubs = location.hubs.findAll{ it.type == physicalgraph.device.HubType.PHYSICAL }
|
||||
//log.debug "hub count: ${hubs.size()}"
|
||||
//log.debug "hubID: ${hubID}"
|
||||
return hubs[0].id
|
||||
}
|
||||
|
||||
|
||||
/* api connection */
|
||||
// get URL
|
||||
private getApiURL() {
|
||||
if (settings.brand == "Craftsman") {
|
||||
return "https://craftexternal.myqdevice.com"
|
||||
} else {
|
||||
return "https://myqexternal.myqdevice.com"
|
||||
}
|
||||
}
|
||||
|
||||
private getApiAppID() {
|
||||
if (settings.brand == "Craftsman") {
|
||||
return "eU97d99kMG4t3STJZO/Mu2wt69yTQwM0WXZA5oZ74/ascQ2xQrLD/yjeVhEQccBZ"
|
||||
} else {
|
||||
return "NWknvuBd7LoFHfXmKNMBcgajXtZEgKUh4V7WNzMidrpUUluDpVYVZx+xT4PCM5Kx"
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP GET call
|
||||
private apiGet(apiPath, apiQuery = [], callback = {}) {
|
||||
// set up query
|
||||
def myHeaders = ""
|
||||
|
||||
|
||||
apiQuery = [ appId: getApiAppID() ] + apiQuery
|
||||
if (state.session.securityToken) {
|
||||
apiQuery = apiQuery + [SecurityToken: state.session.securityToken ]
|
||||
myHeaders = [ "SecurityToken": state.session.securityToken,
|
||||
"MyQApplicationId": getApiAppID() ]
|
||||
}
|
||||
|
||||
try {
|
||||
httpGet([ uri: getApiURL(), path: apiPath, headers: myHeaders, query: apiQuery ]) { response -> callback(response) }
|
||||
} catch (SocketException e) {
|
||||
//sendAlert("API Error: $e")
|
||||
log.debug "API Error: $e"
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP PUT call
|
||||
private apiPut(apiPath, apiBody = [], callback = {}) {
|
||||
// set up body
|
||||
apiBody = [ ApplicationId: getApiAppID() ] + apiBody
|
||||
if (state.session.securityToken) { apiBody = apiBody + [SecurityToken: state.session.securityToken ] }
|
||||
|
||||
def myHeaders = ""
|
||||
|
||||
// set up query
|
||||
def apiQuery = [ appId: getApiAppID() ]
|
||||
if (state.session.securityToken) {
|
||||
apiQuery = apiQuery + [SecurityToken: state.session.securityToken ]
|
||||
myHeaders = [ "SecurityToken": state.session.securityToken,
|
||||
"MyQApplicationId": getApiAppID() ]
|
||||
}
|
||||
|
||||
try {
|
||||
httpPut([ uri: getApiURL(), path: apiPath, headers: myHeaders, contentType: "application/json; charset=utf-8", body: apiBody, query: apiQuery ]) { response -> callback(response) }
|
||||
} catch (SocketException e) {
|
||||
log.debug "API Error: $e"
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP POST call
|
||||
private apiPostLogin(apiPath, apiBody = [], callback = {}) {
|
||||
// set up body
|
||||
apiBody = apiBody
|
||||
def myHeaders = [ "User-Agent": "Chamberlain/3.73",
|
||||
"BrandId": "2",
|
||||
"ApiVersion": "4.1",
|
||||
"Culture": "en",
|
||||
"MyQApplicationId": getApiAppID() ]
|
||||
try {
|
||||
httpPost([ uri: getApiURL(), path: apiPath, headers: myHeaders, contentType: "application/json; charset=utf-8", body: apiBody ]) { response -> callback(response) }
|
||||
} catch (SocketException e) {
|
||||
log.debug "API Error: $e"
|
||||
}
|
||||
}
|
||||
|
||||
// Get Device ID
|
||||
def getChildDeviceID(child) {
|
||||
return child.device.deviceNetworkId.split("\\|")[2]
|
||||
}
|
||||
|
||||
// Get single device status
|
||||
def getDeviceStatus(child) {
|
||||
return state.data[child.device.deviceNetworkId].status
|
||||
}
|
||||
|
||||
// Get single device last activity
|
||||
def getDeviceLastActivity(child) {
|
||||
return state.data[child.device.deviceNetworkId].lastAction.toLong()
|
||||
}
|
||||
|
||||
// Send command to start or stop
|
||||
def sendCommand(child, attributeName, attributeValue) {
|
||||
state.lastCommandSent = now()
|
||||
if (login()) {
|
||||
//Send command
|
||||
apiPut("/api/v4/deviceattribute/putdeviceattribute", [ MyQDeviceId: getChildDeviceID(child), AttributeName: attributeName, AttributeValue: attributeValue ])
|
||||
|
||||
if ((attributeName == "desireddoorstate") && (attributeValue == 0)) { // if we are closing, check if we have an Acceleration sensor, if so, "waiting" until it moves
|
||||
def firstDoor = doors[0]
|
||||
if (doors instanceof String) firstDoor = doors
|
||||
def doorDNI = child.device.deviceNetworkId
|
||||
switch (doorDNI) {
|
||||
case firstDoor:
|
||||
if (door1Sensor){if (door1Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("closing")}
|
||||
break
|
||||
case doors[1]:
|
||||
if (door2Sensor){if (door2Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("closing")}
|
||||
break
|
||||
case doors[2]:
|
||||
if (door3Sensor){if (door3Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("closing")}
|
||||
break
|
||||
case doors[3]:
|
||||
if (door4Sensor){if (door4Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("closing")}
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
def getVersionInfo(oldVersion, newVersion){
|
||||
def params = [
|
||||
uri: 'http://www.fantasyaftermath.com/getMyQVersion/' + oldVersion + '/' + newVersion,
|
||||
contentType: 'application/json'
|
||||
]
|
||||
asynchttp_v1.get('responseHandlerMethod', params)
|
||||
}
|
||||
|
||||
def responseHandlerMethod(response, data) {
|
||||
if (response.hasError()) {
|
||||
log.error "response has error: $response.errorMessage"
|
||||
} else {
|
||||
def results = response.json
|
||||
state.latestSmartAppVersion = results.SmartApp;
|
||||
state.latestDoorVersion = results.DoorDevice;
|
||||
state.latestDoorNoSensorVersion = results.DoorDeviceNoSensor;
|
||||
state.latestLightVersion = results.LightDevice;
|
||||
}
|
||||
|
||||
log.debug "previousVersion: " + state.previousVersion
|
||||
log.debug "installedVersion: " + state.thisSmartAppVersion
|
||||
log.debug "latestVersion: " + state.latestSmartAppVersion
|
||||
log.debug "doorVersion: " + state.latestDoorVersion
|
||||
log.debug "doorNoSensorVersion: " + state.latestDoorNoSensorVersion
|
||||
log.debug "lightVersion: " + state.latestLightVersion
|
||||
}
|
||||
|
||||
def versionCheck(){
|
||||
state.versionWarning = ""
|
||||
state.thisDoorVersion = ""
|
||||
state.thisDoorNoSensorVersion = ""
|
||||
state.thisLightVersion = ""
|
||||
state.versionWarning = ""
|
||||
|
||||
def usesDoorDev = false
|
||||
def usesDoorNoSensorDev = false
|
||||
def usesLightControllerDev = false
|
||||
|
||||
getChildDevices().each { childDevice ->
|
||||
|
||||
try {
|
||||
def devType = childDevice.getTypeName()
|
||||
|
||||
if (devType != "Momentary Button Tile"){
|
||||
if (devType == "MyQ Garage Door Opener"){
|
||||
usesDoorDev = true
|
||||
state.thisDoorVersion = childDevice.showVersion()
|
||||
}
|
||||
if (devType == "MyQ Garage Door Opener-NoSensor"){
|
||||
usesDoorNoSensorDev = true
|
||||
state.thisDoorNoSensorVersion = childDevice.showVersion()
|
||||
}
|
||||
if (devType == "MyQ Light Controller"){
|
||||
usesLightControllerDev = true
|
||||
state.thisLightVersion = childDevice.showVersion()
|
||||
}
|
||||
}
|
||||
|
||||
} catch (MissingPropertyException e) {
|
||||
log.debug "API Error: $e"
|
||||
}
|
||||
}
|
||||
|
||||
//log.debug "This door (no sensor) version: " + state.thisDoorNoSensorVersion
|
||||
//log.debug "This door version: " + state.thisDoorVersion
|
||||
//log.debug "This light version: " + state.thisLightVersion
|
||||
|
||||
if (state.thisSmartAppVersion != state.latestSmartAppVersion) {
|
||||
state.versionWarning = state.versionWarning + "Your SmartApp version (" + state.thisSmartAppVersion + ") is not the latest version (" + state.latestSmartAppVersion + ")\n\n"
|
||||
}
|
||||
if (usesDoorDev && state.thisDoorVersion != state.latestDoorVersion) {
|
||||
state.versionWarning = state.versionWarning + "Your MyQ Door device version (" + state.thisDoorVersion + ") is not the latest version (" + state.latestDoorVersion + ")\n\n"
|
||||
}
|
||||
if (usesDoorNoSensorDev && state.thisDoorNoSensorVersion != state.latestDoorNoSensorVersion) {
|
||||
state.versionWarning = state.versionWarning + "Your MyQ Door (No-sensor) device version (" + state.thisDoorNoSensorVersion + ") is not the latest version (" + state.latestDoorNoSensorVersion + ")\n\n"
|
||||
}
|
||||
if (usesLightControllerDev && state.thisLightVersion != state.latestLightVersion) {
|
||||
state.versionWarning = state.versionWarning + "Your MyQ Light Controller device version (" + state.thisLightVersion + ") is not the latest version (" + state.latestLightVersion + ")\n\n"
|
||||
}
|
||||
log.debug state.versionWarning
|
||||
}
|
||||
|
||||
|
||||
def notify(message){
|
||||
sendNotificationEvent(message)
|
||||
}
|
||||
@@ -24,7 +24,10 @@ metadata {
|
||||
capability "Temperature Measurement"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x20, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x30, 0x9C, 0x98, 0x7A", outClusters: ""
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x20, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x30, 0x9C, 0x98, 0x7A", outClusters: ""
|
||||
fingerprint mfr:"010F", prod:"0801", model:"2001"
|
||||
fingerprint mfr:"010F", prod:"0801", model:"1001"
|
||||
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Aeon Multisensor 6", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "Aeon Multisensor 6", namespace: "smartthings", author: "SmartThings", runLocally: true, minHubCoreVersion: '000.020.00008', executeCommandsLocally: true) {
|
||||
capability "Motion Sensor"
|
||||
capability "Temperature Measurement"
|
||||
capability "Relative Humidity Measurement"
|
||||
|
||||
@@ -25,7 +25,6 @@ metadata {
|
||||
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "GE In-Wall Smart Dimmer"
|
||||
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "GE In-Wall Smart Dimmer"
|
||||
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "GE Plug-In Smart Dimmer"
|
||||
fingerprint mfr:"0063", prod:"4944", model:"3034", deviceJoinName: "GE In-Wall Smart Fan Control"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -46,15 +46,16 @@
|
||||
capability "Illuminance Measurement"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
capability "Health Check"
|
||||
|
||||
command "resetParams2StDefaults"
|
||||
command "listCurrentParams"
|
||||
command "updateZwaveParam"
|
||||
command "test"
|
||||
command "configure"
|
||||
command "resetParams2StDefaults"
|
||||
command "listCurrentParams"
|
||||
command "updateZwaveParam"
|
||||
command "test"
|
||||
command "configure"
|
||||
|
||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x84,0x85,0x80,0x8F,0x56,0x72,0x86,0x70,0x8E,0x31,0x9C,0xEF,0x30,0x31,0x9C"
|
||||
fingerprint mfr:"010F", prod:"0800", model:"2001"
|
||||
fingerprint mfr:"010F", prod:"0800", model:"1001"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -171,8 +171,9 @@ private Map getBatteryResult(rawValue) {
|
||||
def pct = batteryMap[volts]
|
||||
result.value = pct
|
||||
} else {
|
||||
def minVolts = 2.4
|
||||
def maxVolts = 2.7
|
||||
def useOldBatt = shouldUseOldBatteryReporting()
|
||||
def minVolts = useOldBatt ? 2.1 : 2.4
|
||||
def maxVolts = useOldBatt ? 3.0 : 2.7
|
||||
// Get the current battery percentage as a multiplier 0 - 1
|
||||
def curValVolts = Integer.parseInt(device.currentState("battery")?.value ?: "100") / 100.0
|
||||
// Find the corresponding voltage from our range
|
||||
@@ -183,15 +184,16 @@ private Map getBatteryResult(rawValue) {
|
||||
// OR we have received the same reading twice in a row
|
||||
// OR we don't currently have a battery reading
|
||||
// OR the value we just received is at least 2 steps off from the last reported value
|
||||
if(state?.lastVolts == null || state?.lastVolts == volts || device.currentState("battery")?.value == null || Math.abs(curValVolts - volts) > 0.1) {
|
||||
// OR the device's firmware is older than 1.15.7
|
||||
if(useOldBatt || state?.lastVolts == null || state?.lastVolts == volts || device.currentState("battery")?.value == null || Math.abs(curValVolts - volts) > 0.1) {
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
} else {
|
||||
// Don't update as we want to smooth the battery values
|
||||
result = null
|
||||
// Don't update as we want to smooth the battery values, but do report the last battery state for record keeping purposes
|
||||
result.value = device.currentState("battery").value
|
||||
}
|
||||
state.lastVolts = volts
|
||||
}
|
||||
@@ -237,3 +239,22 @@ def configure() {
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
private shouldUseOldBatteryReporting() {
|
||||
def isFwVersionLess = true // By default use the old battery reporting
|
||||
def deviceFwVer = "${device.getFirmwareVersion()}"
|
||||
def deviceVersion = deviceFwVer.tokenize('.') // We expect the format ###.###.### where ### is some integer
|
||||
|
||||
if (deviceVersion.size() == 3) {
|
||||
def targetVersion = [1, 15, 7] // Centralite Firmware 1.15.7 contains battery smoothing fixes, so versions before that should NOT be smoothed
|
||||
def devMajor = deviceVersion[0] as int
|
||||
def devMinor = deviceVersion[1] as int
|
||||
def devBuild = deviceVersion[2] as int
|
||||
|
||||
isFwVersionLess = ((devMajor < targetVersion[0]) ||
|
||||
(devMajor == targetVersion[0] && devMinor < targetVersion[1]) ||
|
||||
(devMajor == targetVersion[0] && devMinor == targetVersion[1] && devBuild < targetVersion[2]))
|
||||
}
|
||||
|
||||
return isFwVersionLess // If f/w version is less than 1.15.7 then do NOT smooth battery reports and use the old reporting
|
||||
}
|
||||
|
||||
@@ -273,8 +273,10 @@ private Map getBatteryResult(rawValue) {
|
||||
def pct = batteryMap[volts]
|
||||
result.value = pct
|
||||
} else {
|
||||
def useOldBatt = shouldUseOldBatteryReporting()
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 2.7
|
||||
def maxVolts = useOldBatt ? 3.0 : 2.7
|
||||
|
||||
// Get the current battery percentage as a multiplier 0 - 1
|
||||
def curValVolts = Integer.parseInt(device.currentState("battery")?.value ?: "100") / 100.0
|
||||
// Find the corresponding voltage from our range
|
||||
@@ -285,15 +287,16 @@ private Map getBatteryResult(rawValue) {
|
||||
// OR we have received the same reading twice in a row
|
||||
// OR we don't currently have a battery reading
|
||||
// OR the value we just received is at least 2 steps off from the last reported value
|
||||
if(state?.lastVolts == null || state?.lastVolts == volts || device.currentState("battery")?.value == null || Math.abs(curValVolts - volts) > 0.1) {
|
||||
// OR the device's firmware is older than 1.15.7
|
||||
if(useOldBatt || state?.lastVolts == null || state?.lastVolts == volts || device.currentState("battery")?.value == null || Math.abs(curValVolts - volts) > 0.1) {
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
} else {
|
||||
// Don't update as we want to smooth the battery values
|
||||
result = null
|
||||
// Don't update as we want to smooth the battery values, but do report the last battery state for record keeping purposes
|
||||
result.value = device.currentState("battery").value
|
||||
}
|
||||
state.lastVolts = volts
|
||||
}
|
||||
@@ -413,6 +416,25 @@ private getManufacturerCode() {
|
||||
}
|
||||
}
|
||||
|
||||
private shouldUseOldBatteryReporting() {
|
||||
def isFwVersionLess = true // By default use the old battery reporting
|
||||
def deviceFwVer = "${device.getFirmwareVersion()}"
|
||||
def deviceVersion = deviceFwVer.tokenize('.') // We expect the format ###.###.### where ### is some integer
|
||||
|
||||
if (deviceVersion.size() == 3) {
|
||||
def targetVersion = [1, 15, 7] // Centralite Firmware 1.15.7 contains battery smoothing fixes, so versions before that should NOT be smoothed
|
||||
def devMajor = deviceVersion[0] as int
|
||||
def devMinor = deviceVersion[1] as int
|
||||
def devBuild = deviceVersion[2] as int
|
||||
|
||||
isFwVersionLess = ((devMajor < targetVersion[0]) ||
|
||||
(devMajor == targetVersion[0] && devMinor < targetVersion[1]) ||
|
||||
(devMajor == targetVersion[0] && devMinor == targetVersion[1] && devBuild < targetVersion[2]))
|
||||
}
|
||||
|
||||
return isFwVersionLess // If f/w version is less than 1.15.7 then do NOT smooth battery reports and use the old reporting
|
||||
}
|
||||
|
||||
private hexToInt(value) {
|
||||
new BigInteger(value, 16)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Simulated Dimmer Switch", namespace: "smartthings/testing", author: "SmartThings") {
|
||||
definition (name: "Simulated Dimmer Switch", namespace: "smartthings/testing", author: "SmartThings", runLocally: true, minHubCoreVersion: '000.020.00008', executeCommandsLocally: true) {
|
||||
capability "Actuator"
|
||||
capability "Sensor"
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
metadata {
|
||||
|
||||
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
|
||||
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob", runLocally: true, minHubCoreVersion: '000.020.00008', executeCommandsLocally: true) {
|
||||
capability "Switch"
|
||||
capability "Relay Switch"
|
||||
capability "Sensor"
|
||||
|
||||
@@ -30,8 +30,13 @@ metadata {
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY PAR38 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart PAR38 Soft White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart BR30 Soft White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E11-G13", deviceJoinName: "Sengled Element Classic A19"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E12-N14", deviceJoinName: "Sengled Element Classic BR30"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E11-G13", deviceJoinName: "Sengled Element Classic"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E11-G14", deviceJoinName: "Sengled Element Classic"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E11-G23", deviceJoinName: "Sengled Element Classic"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E11-G33", deviceJoinName: "Sengled Element Classic"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E12-N13", deviceJoinName: "Sengled Element Classic"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E12-N14", deviceJoinName: "Sengled Element Classic"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E12-N15", deviceJoinName: "Sengled Element Classic"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL6HD", deviceJoinName: "Leviton Dimmer Switch"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL3HL", deviceJoinName: "Leviton Lumina RF Plug-In Dimmer"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL1KD", deviceJoinName: "Leviton Lumina RF Dimmer Switch"
|
||||
|
||||
@@ -32,7 +32,7 @@ metadata {
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD226/246 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD226/246 TSDB", deviceJoinName: "Yale Assure Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_5", deviceJoinName: "Kwikset 5-Button Deadbolt"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_LEVER_5", deviceJoinName: "Kwikset 5-Button Lever"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt"
|
||||
|
||||
@@ -38,7 +38,10 @@ metadata {
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "SYLVANIA Smart A19 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-A19NAE26", deviceJoinName: "Sengled Element plus"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-A19NAE26", deviceJoinName: "Sengled Element Plus"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-A191AE26W", deviceJoinName: "Sengled Element Plus"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-A60EAB22", deviceJoinName: "Sengled Element Plus"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-A60EAE27", deviceJoinName: "Sengled Element Plus"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, 0B05, FC01, FC08", outClusters: "0003, 0019", manufacturer: "LEDVANCE", model: "A19 TW 10 year", deviceJoinName: "SYLVANIA Smart 10Y A19 TW"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Conv Under Cabinet TW", deviceJoinName: "SYLVANIA Smart Convertible Under Cabinet"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "ColorstripRGBW", deviceJoinName: "SYLVANIA Smart Convertible Under Cabinet"
|
||||
|
||||
@@ -31,6 +31,13 @@ metadata {
|
||||
fingerprint mfr:"001D", prod:"0401", model:"0334", deviceJoinName: "Leviton 600W Incandescent Dimmer"
|
||||
fingerprint mfr:"0111", prod:"8200", model:"0200", deviceJoinName: "Remotec Technology Plug-In Dimmer"
|
||||
fingerprint mfr:"1104", prod:"001D", model:"0501", deviceJoinName: "Leviton 1000W Incandescant Dimmer"
|
||||
fingerprint mfr:"0039", prod:"5044", model:"3033", deviceJoinName: "Honeywell Z-Wave Plug-in Dimmer (Dual Outlet)"
|
||||
fingerprint mfr:"0039", prod:"5044", model:"3038", deviceJoinName: "Honeywell Z-Wave Plug-in Dimmer"
|
||||
fingerprint mfr:"0039", prod:"4944", model:"3038", deviceJoinName: "Honeywell Z-Wave In-Wall Smart Dimmer"
|
||||
fingerprint mfr:"0039", prod:"4944", model:"3130", deviceJoinName: "Honeywell Z-Wave In-Wall Smart Toggle Dimmer"
|
||||
fingerprint mfr:"0063", prod:"4944", model:"3034", deviceJoinName: "GE In-Wall Smart Fan Control"
|
||||
fingerprint mfr:"0063", prod:"4944", model:"3131", deviceJoinName: "GE In-Wall Smart Fan Control"
|
||||
fingerprint mfr:"0039", prod:"4944", model:"3131", deviceJoinName: "Honeywell Z-Wave Plus In-Wall Fan Speed Control"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -81,6 +88,7 @@ metadata {
|
||||
def installed(){
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"])
|
||||
response(refresh())
|
||||
}
|
||||
|
||||
def updated(){
|
||||
|
||||
@@ -48,7 +48,7 @@ metadata {
|
||||
fingerprint mfr:"0129", prod:"0004", model:"0800", deviceJoinName: "Yale Push Button Deadbolt Door Lock" // YRD110
|
||||
fingerprint mfr:"0129", prod:"0004", model:"0000", deviceJoinName: "Yale Push Button Deadbolt Door Lock" // YRD210
|
||||
fingerprint mfr:"0129", prod:"0001", model:"0000", deviceJoinName: "Yale Push Button Lever Door Lock" // YRD210
|
||||
fingerprint mfr:"0129", prod:"8002", model:"0600", deviceJoinName: "Yale Assure Lock with Bluetooth" //YRD416, YRD426, YRD446
|
||||
fingerprint mfr:"0129", prod:"8002", model:"0600", deviceJoinName: "Yale Assure Lock" //YRD416, YRD426, YRD446
|
||||
fingerprint mfr:"0129", prod:"0007", model:"0001", deviceJoinName: "Yale Keyless Connected Smart Door Lock"
|
||||
fingerprint mfr:"0129", prod:"8004", model:"0600", deviceJoinName: "Yale Assure Lock Push Button Deadbolt" //YRD216
|
||||
// Samsung
|
||||
@@ -387,7 +387,7 @@ private def handleAccessAlarmReport(cmd) {
|
||||
break
|
||||
case 5: // Locked with keypad
|
||||
if (cmd.eventParameter || cmd.alarmLevel) {
|
||||
codeID = cmd.eventParameter[2] ?: cmd.alarmLevel
|
||||
codeID = readCodeSlotId(cmd)
|
||||
codeName = getCodeName(lockCodes, codeID)
|
||||
map.descriptionText = "Locked by \"$codeName\""
|
||||
map.data = [ usedCode: codeID, codeName: codeName, method: "keypad" ]
|
||||
@@ -398,7 +398,7 @@ private def handleAccessAlarmReport(cmd) {
|
||||
break
|
||||
case 6: // Unlocked with keypad
|
||||
if (cmd.eventParameter || cmd.alarmLevel) {
|
||||
codeID = cmd.eventParameter[2] ?: cmd.alarmLevel
|
||||
codeID = readCodeSlotId(cmd)
|
||||
codeName = getCodeName(lockCodes, codeID)
|
||||
map.descriptionText = "Unlocked by \"$codeName\""
|
||||
map.data = [ usedCode: codeID, codeName: codeName, method: "keypad" ]
|
||||
@@ -431,7 +431,7 @@ private def handleAccessAlarmReport(cmd) {
|
||||
break
|
||||
case 0xD: // User code deleted
|
||||
if (cmd.eventParameter || cmd.alarmLevel) {
|
||||
codeID = cmd.eventParameter[2] ?: cmd.alarmLevel
|
||||
codeID = readCodeSlotId(cmd)
|
||||
if (lockCodes[codeID.toString()]) {
|
||||
codeName = getCodeName(lockCodes, codeID)
|
||||
map = [ name: "codeChanged", value: "$codeID deleted", isStateChange: true ]
|
||||
@@ -443,7 +443,7 @@ private def handleAccessAlarmReport(cmd) {
|
||||
break
|
||||
case 0xE: // Master or user code changed/set
|
||||
if (cmd.eventParameter || cmd.alarmLevel) {
|
||||
codeID = cmd.eventParameter[2] ?: cmd.alarmLevel
|
||||
codeID = readCodeSlotId(cmd)
|
||||
if(codeID == 0 && isKwiksetLock()) {
|
||||
//Ignoring this AlarmReport as Kwikset reports codeID 0 when all slots are full and user tries to set another lock code manually
|
||||
//Kwikset locks don't send AlarmReport when Master code is set
|
||||
@@ -464,7 +464,7 @@ private def handleAccessAlarmReport(cmd) {
|
||||
break
|
||||
case 0xF: // Duplicate Pin-code error
|
||||
if (cmd.eventParameter || cmd.alarmLevel) {
|
||||
codeID = cmd.eventParameter[2] ?: cmd.alarmLevel
|
||||
codeID = readCodeSlotId(cmd)
|
||||
clearStateForSlot(codeID)
|
||||
map = [ name: "codeChanged", value: "$codeID failed", descriptionText: "User code is duplicate and not added",
|
||||
isStateChange: true, data: [isCodeDuplicate: true] ]
|
||||
@@ -592,14 +592,14 @@ private def handleAlarmReportUsingAlarmType(cmd) {
|
||||
case 19: // Unlocked with keypad
|
||||
map = [ name: "lock", value: "unlocked" ]
|
||||
if (cmd.alarmLevel != null) {
|
||||
codeID = cmd.alarmLevel
|
||||
codeID = readCodeSlotId(cmd)
|
||||
codeName = getCodeName(lockCodes, codeID)
|
||||
map.descriptionText = "Unlocked by \"$codeName\""
|
||||
map.data = [ usedCode: codeID, codeName: codeName, method: "keypad" ]
|
||||
}
|
||||
break
|
||||
case 18: // Locked with keypad
|
||||
codeID = cmd.alarmLevel
|
||||
codeID = readCodeSlotId(cmd)
|
||||
map = [ name: "lock", value: "locked" ]
|
||||
// Kwikset lock reporting code id as 0 when locked using the lock keypad button
|
||||
if (isKwiksetLock() && codeID == 0) {
|
||||
@@ -646,7 +646,7 @@ private def handleAlarmReportUsingAlarmType(cmd) {
|
||||
result << createEvent(name: "lockCodes", value: util.toJson([:]), displayed: false, descriptionText: "'lockCodes' attribute updated")
|
||||
break
|
||||
case 33: // User code deleted
|
||||
codeID = cmd.alarmLevel
|
||||
codeID = readCodeSlotId(cmd)
|
||||
if (lockCodes[codeID.toString()]) {
|
||||
codeName = getCodeName(lockCodes, codeID)
|
||||
map = [ name: "codeChanged", value: "$codeID deleted", isStateChange: true ]
|
||||
@@ -660,7 +660,7 @@ private def handleAlarmReportUsingAlarmType(cmd) {
|
||||
break
|
||||
case 13:
|
||||
case 112: // Master or user code changed/set
|
||||
codeID = cmd.alarmLevel
|
||||
codeID = readCodeSlotId(cmd)
|
||||
if(codeID == 0 && isKwiksetLock()) {
|
||||
//Ignoring this AlarmReport as Kwikset reports codeID 0 when all slots are full and user tries to set another lock code manually
|
||||
//Kwikset locks don't send AlarmReport when Master code is set
|
||||
@@ -681,7 +681,7 @@ private def handleAlarmReportUsingAlarmType(cmd) {
|
||||
break
|
||||
case 34:
|
||||
case 113: // Duplicate Pin-code error
|
||||
codeID = cmd.alarmLevel
|
||||
codeID = readCodeSlotId(cmd)
|
||||
clearStateForSlot(codeID)
|
||||
map = [ name: "codeChanged", value: "$codeID failed", descriptionText: "User code is duplicate and not added",
|
||||
isStateChange: true, data: [isCodeDuplicate: true] ]
|
||||
@@ -1657,3 +1657,17 @@ def isYaleLock() {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic function for reading code Slot ID from AlarmReport command
|
||||
* @param cmd: The AlarmReport command
|
||||
* @return user code slot id
|
||||
*/
|
||||
def readCodeSlotId(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
|
||||
if(cmd.numberOfEventParameters == 1) {
|
||||
return cmd.eventParameter[0]
|
||||
} else if(cmd.numberOfEventParameters >= 3) {
|
||||
return cmd.eventParameter[2]
|
||||
}
|
||||
return cmd.alarmLevel
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Z-Wave Plus Door/Window Sensor", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "x.com.st.d.sensor.contact") {
|
||||
definition (name: "Z-Wave Plus Door/Window Sensor", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "x.com.st.d.sensor.contact", runLocally: true, minHubCoreVersion: '000.020.00008', executeCommandsLocally: true) {
|
||||
capability "Contact Sensor"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
|
||||
@@ -25,6 +25,12 @@ metadata {
|
||||
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "GE Wall Switch"
|
||||
fingerprint mfr:"0063", prod:"5052", deviceJoinName: "GE Plug-In Switch"
|
||||
fingerprint mfr:"0113", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
|
||||
fingerprint mfr:"0039", prod:"5052", model:"3038", deviceJoinName: "Honeywell Z-Wave Plug-in Switch"
|
||||
fingerprint mfr:"0039", prod:"5052", model:"3033", deviceJoinName: "Honeywell Z-Wave Plug-in Switch (Dual Outlet)"
|
||||
fingerprint mfr:"0039", prod:"4F50", model:"3032", deviceJoinName: "Honeywell Z-Wave Plug-in Outdoor Smart Switch"
|
||||
fingerprint mfr:"0039", prod:"4952", model:"3036", deviceJoinName: "Honeywell Z-Wave In-Wall Smart Switch"
|
||||
fingerprint mfr:"0039", prod:"4952", model:"3037", deviceJoinName: "Honeywell Z-Wave In-Wall Smart Toggle Switch"
|
||||
fingerprint mfr:"0039", prod:"4952", model:"3133", deviceJoinName: "Honeywell Z-Wave In-Wall Tamper Resistant Duplex Receptacle"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
Reference in New Issue
Block a user