From ad8a66533f2a61a03710b548efa83ce1c4374f86 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:12:38 -0400 Subject: [PATCH 01/74] Update switchbotSystem added keypad touch and read child device details --- switchbotSystem | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index fd91e3b..99cccb5 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -221,19 +221,25 @@ def createChildDevices() if(["Plug Mini (US)", "Plug Mini (JP)"].contains(it.deviceType)) { it.deviceType = "Plug Mini" } if(it.deviceType == "Color Bulb") { it.deviceType = "Strip Light" } if(it.deviceType == "Curtain3") { it.deviceType = "Curtain" } - - if((it.deviceId && it.deviceName && it.deviceType) && !findChildDevice(it.deviceId, it.deviceType)) + + def childDevice = findChildDevice(it.deviceId, it.deviceType) + if(it.deviceId && it.deviceName && it.deviceTyp) { - if(["Curtain", "Blind Tilt"].contains(it.deviceType)) - { - // only create children for ungrouped Curtains and group masters - if(!((it.group != true) || (it.master == true))) + if (!childDevice) { + if(["Curtain", "Blind Tilt"].contains(it.deviceType)) { - continue + // only create children for ungrouped Curtains and group masters + if(!((it.group != true) || (it.master == true))) + { + continue + } } + + createChildDevice(it.deviceName, it.deviceId, it.deviceType) + } + else { + childDevice.readDeviceDetails(it) } - - createChildDevice(it.deviceName, it.deviceId, it.deviceType) } } @@ -294,7 +300,7 @@ def createChildDevice(name, id, deviceType) { try { - def customDevTypes = ["Bot", "Curtain", "Meter", "IR Device", "Humidifier", "Strip Light", "Smart Lock", "Motion Sensor", "Contact Sensor", "Plug Mini", "Blind Tilt"] + def customDevTypes = ["Bot", "Curtain", "Meter", "IR Device", "Humidifier", "Strip Light", "Smart Lock", "Motion Sensor", "Contact Sensor", "Plug Mini", "Blind Tilt", "Keypad Touch"] def genericDevTypes = [] deviceType = deviceType.toString() From 08e2cf46463a47742a80544aba31681b14fd7170 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:12:55 -0400 Subject: [PATCH 02/74] Update switchbotSystem --- switchbotSystem | 1 + 1 file changed, 1 insertion(+) diff --git a/switchbotSystem b/switchbotSystem index 99cccb5..7da5df1 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -18,6 +18,7 @@ limitations under the License. Change history: +0.9.24 - dsegall - added Keypad Touch 0.9.21 - tomw - Added temperature, humidity, and lightLevel support for Hub 2 (in SwitchBot Meter driver). Added Indoor/Outdoor Thermo-Hygrometer (in SwitchBot Meter driver). From df0bdb46dfa7e83ee2a6a1dd216257104cb97aae Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:13:34 -0400 Subject: [PATCH 03/74] Update switchbotBlindTilt --- switchbotBlindTilt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/switchbotBlindTilt b/switchbotBlindTilt index 4e21625..4cb2f06 100644 --- a/switchbotBlindTilt +++ b/switchbotBlindTilt @@ -247,3 +247,6 @@ def correctedPositions(rawPos) return retVal } + +def readDeviceDetails(details) { +} From 2a2ebe929ace3db34abaa89d610dfbf25cb3a691 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:13:44 -0400 Subject: [PATCH 04/74] Update switchbotBot --- switchbotBot | 3 +++ 1 file changed, 3 insertions(+) diff --git a/switchbotBot b/switchbotBot index 3df8363..9590c24 100644 --- a/switchbotBot +++ b/switchbotBot @@ -98,3 +98,6 @@ def push(buttonNumber = 0) runIn(2, refresh) } } + +def readDeviceDetails(details) { +} From 693026a46eb1a327bd515239b97670ea265ba548 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:13:54 -0400 Subject: [PATCH 05/74] Update switchbotContactSensor --- switchbotContactSensor | 2 ++ 1 file changed, 2 insertions(+) diff --git a/switchbotContactSensor b/switchbotContactSensor index 65749e4..b649989 100644 --- a/switchbotContactSensor +++ b/switchbotContactSensor @@ -71,3 +71,5 @@ def parse(body) } } +def readDeviceDetails(details) { +} From 48c8ad49dd2c5deb1984368f6adac8b998bb540d Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:14:02 -0400 Subject: [PATCH 06/74] Update switchbotCurtain --- switchbotCurtain | 3 +++ 1 file changed, 3 insertions(+) diff --git a/switchbotCurtain b/switchbotCurtain index a36d13a..8d53db3 100644 --- a/switchbotCurtain +++ b/switchbotCurtain @@ -203,3 +203,6 @@ def correctedPositions(rawPos) return retVal } + +def readDeviceDetails(details) { +} From 61bc84c301188ed1b59bdc45f244c4e086c62da5 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:14:52 -0400 Subject: [PATCH 07/74] Update switchbotEventsApp --- switchbotEventsApp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/switchbotEventsApp b/switchbotEventsApp index 0c99e28..2345c93 100644 --- a/switchbotEventsApp +++ b/switchbotEventsApp @@ -203,7 +203,8 @@ import groovy.transform.Field WoPlugJP: [name: "Plug Mini", attrs: commonSwitchAttrsMap], WoPlugUS: [name: "Plug Mini", attrs: commonSwitchAttrsMap], WoPresence: [name: "Motion Sensor", attrs: motionSensorAttrsMap], - WoStrip: [name: "Strip Light", attrs: commonSwitchAttrsMap], + WoStrip: [name: "Strip Light", attrs: commonSwitchAttrsMap], + WoKeypadTouch: [name: "Keypad Touch"], ] static @Field allDevsAttrs = From cb948cd3f4d0816627a419c2cdb56d4d0c3e7d4b Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:15:01 -0400 Subject: [PATCH 08/74] Update switchbotHumidifier --- switchbotHumidifier | 3 +++ 1 file changed, 3 insertions(+) diff --git a/switchbotHumidifier b/switchbotHumidifier index cc60667..4608179 100644 --- a/switchbotHumidifier +++ b/switchbotHumidifier @@ -170,3 +170,6 @@ def setMode(mode) runIn(2, refresh) } } + +def readDeviceDetails(details) { +} From 97200098f25645854c474bdde808c8870efc13a7 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:15:12 -0400 Subject: [PATCH 09/74] Update switchbotIRDevice --- switchbotIRDevice | 3 +++ 1 file changed, 3 insertions(+) diff --git a/switchbotIRDevice b/switchbotIRDevice index 707a4bf..82dd52e 100644 --- a/switchbotIRDevice +++ b/switchbotIRDevice @@ -71,6 +71,9 @@ def sendCommand(command, parameter = "default") } } +def readDeviceDetails(details) { +} + import groovy.transform.Field @Field standardCommands = From 1c2ff4f78051f5e5e37cb71d16b4d52f4bc01299 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:15:20 -0400 Subject: [PATCH 10/74] Update switchbotMeter --- switchbotMeter | 2 ++ 1 file changed, 2 insertions(+) diff --git a/switchbotMeter b/switchbotMeter index 4faa16e..fd07c96 100644 --- a/switchbotMeter +++ b/switchbotMeter @@ -72,3 +72,5 @@ def parse(body) } } +def readDeviceDetails(details) { +} From 1dfc8d4ced33ad45f64bdd18d568eda7dff30a78 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:15:28 -0400 Subject: [PATCH 11/74] Update switchbotMotionSensor --- switchbotMotionSensor | 2 ++ 1 file changed, 2 insertions(+) diff --git a/switchbotMotionSensor b/switchbotMotionSensor index 672b5d7..0dfd71d 100644 --- a/switchbotMotionSensor +++ b/switchbotMotionSensor @@ -62,3 +62,5 @@ def parse(body) } } +def readDeviceDetails(details) { +} From f1569d62447bf9eaabfe46c6a2efbe5237ea08f7 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:15:35 -0400 Subject: [PATCH 12/74] Update switchbotPlugMini --- switchbotPlugMini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/switchbotPlugMini b/switchbotPlugMini index 6bff3e1..005769a 100644 --- a/switchbotPlugMini +++ b/switchbotPlugMini @@ -100,3 +100,6 @@ def toggle() { writeDeviceCommand("toggle") } + +def readDeviceDetails(details) { +} From 85c9e06019b08964c5c5d997b576e28cde688350 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:15:46 -0400 Subject: [PATCH 13/74] Update switchbotSmartLock --- switchbotSmartLock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/switchbotSmartLock b/switchbotSmartLock index 9d590a2..fa0021a 100644 --- a/switchbotSmartLock +++ b/switchbotSmartLock @@ -83,3 +83,5 @@ def unlock() writeDeviceCommand("unlock") } +def readDeviceDetails(details) { +} From 8023f7163c40d6e71b806c561ee4cbe7861c3dc8 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:15:53 -0400 Subject: [PATCH 14/74] Update switchbotStripLight --- switchbotStripLight | 3 +++ 1 file changed, 3 insertions(+) diff --git a/switchbotStripLight b/switchbotStripLight index 9a0a97a..11514bc 100644 --- a/switchbotStripLight +++ b/switchbotStripLight @@ -266,3 +266,6 @@ def tempToColor(temp) return genericName } + +def readDeviceDetails(details) { +} From b81b1beba40a800924de5a63e4c78260a89ffd7b Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:18:18 -0400 Subject: [PATCH 15/74] Create switchbotKeypadTouch --- switchbotKeypadTouch | 91 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 switchbotKeypadTouch diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch new file mode 100644 index 0000000..4a899b1 --- /dev/null +++ b/switchbotKeypadTouch @@ -0,0 +1,91 @@ +/* + +Copyright 2024 - dsegall + +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. + +------------------------------------------- + +Change history: + +0.9.22 dsegall Added Keypad Touch + +*/ + +@Field static final String PERMANENT = "permanent" +@Field static final String TIMED = "timeLimit" +@Field static final String DISPOSABLE = "disposable" +@Field static final String EMERGENCY = "urgent" + +@Field static final String SUCCESS = "success" +@Field static final String FAILED = "failed" +@Field static final String TIMEOUT = "timeout" + +metadata +{ + definition(name: "SwitchBot Keypad Touch", namespace: "tomw", author: "dsegall", importUrl: "") + { + capability "Refresh" + + command "createKey", [[name: "ID*", type: "STRING"], [name: "Password*", type: "STRING"], [name: "Type", type: "ENUM", constraints: [PERMANENT, TIMED, DISPOSABLE, EMERGENCY]], [name: "Start Time", type: "NUMBER"], [name: "End Time", type: "NUMBER"]] + command "deleteKey", [[name: "ID*", type: "STRING"]] + } +} + +def initialize() +{ + refresh() +} + +def refresh() +{ + parent?.refreshFromParent(device) +} + +def parse(body) +{ + if(!body) { return } + + if(null != body.doorState) + { + def dState = body.doorState.toLowerCase() + sendEvent(name: "contact", value: ["open", "closed"].contains(dState) ? dState : "unknown") + } + + if(null != body.lockState) + { + def lState = body.lockState.toLowerCase() + sendEvent(name: "lock", value: ["unlocked", "locked", "jammed"].contains(lState) ? lState : "unknown") + } +} + +def writeDeviceCommand(command, parameter = "default", commandType = "command") +{ + def id = getParent()?.getId(device.getDeviceNetworkId()) + if(id) + { + getParent()?.writeDeviceCommand(id, command, parameter, commandType) + } +} + +def createKey(String name, String code, String type=PERMANENT, long startTime=0, long endTime=0) { + Map params = ["name": name, "type": type, "password": code, "startTime": startTime, "endTime": endTime] + writeDeviceCommand("createKey", params) +} + +def deleteKey(String name) { + writeDeviceCommand("deleteKey", ["name": name]) +} + +def readDeviceDetails(details) { +} From e5b4d8308cb1cf5c2e34caf419862cfd17c25680 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Fri, 22 Mar 2024 07:54:24 -0400 Subject: [PATCH 16/74] Update switchbotSystem --- switchbotSystem | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index 7da5df1..68f1479 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -236,11 +236,11 @@ def createChildDevices() } } - createChildDevice(it.deviceName, it.deviceId, it.deviceType) + childDevice = createChildDevice(it.deviceName, it.deviceId, it.deviceType) } - else { + + if (childDevice) childDevice.readDeviceDetails(it) - } } } From 47923a045f2268428e28f0c42ce54ac90c344397 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Fri, 22 Mar 2024 07:56:31 -0400 Subject: [PATCH 17/74] Update switchbotKeypadTouch --- switchbotKeypadTouch | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 4a899b1..8c68e06 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -40,6 +40,22 @@ metadata command "createKey", [[name: "ID*", type: "STRING"], [name: "Password*", type: "STRING"], [name: "Type", type: "ENUM", constraints: [PERMANENT, TIMED, DISPOSABLE, EMERGENCY]], [name: "Start Time", type: "NUMBER"], [name: "End Time", type: "NUMBER"]] command "deleteKey", [[name: "ID*", type: "STRING"]] } + + preferences + { + section + { + input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: false + } + } +} + +def logDebug(msg) +{ + if (logEnable) + { + log.debug(msg) + } } def initialize() @@ -54,19 +70,7 @@ def refresh() def parse(body) { - if(!body) { return } - - if(null != body.doorState) - { - def dState = body.doorState.toLowerCase() - sendEvent(name: "contact", value: ["open", "closed"].contains(dState) ? dState : "unknown") - } - - if(null != body.lockState) - { - def lState = body.lockState.toLowerCase() - sendEvent(name: "lock", value: ["unlocked", "locked", "jammed"].contains(lState) ? lState : "unknown") - } + logDebug body } def writeDeviceCommand(command, parameter = "default", commandType = "command") From 231d0e193d5f0e217ab745d9122e016de2128700 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 24 Mar 2024 20:49:57 -0400 Subject: [PATCH 18/74] added rereadDevices --- switchbotSystem | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index 68f1479..9dc6a8c 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -97,6 +97,20 @@ def initialize() } } +def rereadDevices() +{ + try + { + readDevices() + createChildDevices(false) + } + catch (Exception e) + { + logDebug("rereadDevices() failed: ${e.message}") + return + } +} + def updated() { configure() @@ -194,7 +208,7 @@ def refreshFromParent(child) } } -def createChildDevices() +def createChildDevices(boolean createNewDevices=true) { def devices = getDevices() @@ -224,9 +238,9 @@ def createChildDevices() if(it.deviceType == "Curtain3") { it.deviceType = "Curtain" } def childDevice = findChildDevice(it.deviceId, it.deviceType) - if(it.deviceId && it.deviceName && it.deviceTyp) + if(it.deviceId && it.deviceName && it.deviceType) { - if (!childDevice) { + if (!childDevice && createNewDevices) { if(["Curtain", "Blind Tilt"].contains(it.deviceType)) { // only create children for ungrouped Curtains and group masters From 273cddfa62d73c6fd4827d780873a674391abce3 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:00:56 -0400 Subject: [PATCH 19/74] added decryptPasscode --- switchbotSystem | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/switchbotSystem b/switchbotSystem index 9dc6a8c..0f5bcd2 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -527,6 +527,23 @@ def hmac_sha256(String secretKey, String data) return org.apache.commons.codec.binary.Base64.encodeBase64String(sign) } +def decryptPasscode(String iv, String encrypted) { + try { + def ivSpec = new javax.crypto.spec.IvParameterSpec(initVector.getBytes("UTF-8")) + def skeySpec = new javax.crypto.spec.SecretKeySpec(key.getBytes("UTF-8"), "AES") + + def cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5PADDING") + cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec) + byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted)) + + return new String(original) + } catch (Exception ex) { + ex.printStackTrace() + } + + return null +} + def httpGetExec(params, throwToCaller = false) { logDebug("httpGetExec(${params})") From d417d6439b4044e2a5535b30d59a9eb8014ba17c Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:33:29 -0400 Subject: [PATCH 20/74] fixed variables --- switchbotSystem | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index 0f5bcd2..b128a1c 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -529,8 +529,8 @@ def hmac_sha256(String secretKey, String data) def decryptPasscode(String iv, String encrypted) { try { - def ivSpec = new javax.crypto.spec.IvParameterSpec(initVector.getBytes("UTF-8")) - def skeySpec = new javax.crypto.spec.SecretKeySpec(key.getBytes("UTF-8"), "AES") + def ivSpec = new javax.crypto.spec.IvParameterSpec(iv.getBytes("UTF-8")) + def skeySpec = new javax.crypto.spec.SecretKeySpec(secretKey.getBytes("UTF-8"), "AES") def cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5PADDING") cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec) From e4447f24cf5e9377bbbb6fa84b19a12b4c4e45d0 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:34:42 -0400 Subject: [PATCH 21/74] add package path --- switchbotSystem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbotSystem b/switchbotSystem index b128a1c..b75d151 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -534,7 +534,7 @@ def decryptPasscode(String iv, String encrypted) { def cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5PADDING") cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec) - byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted)) + byte[] original = cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(encrypted)) return new String(original) } catch (Exception ex) { From e5175088fe1bd93a5b3cf85b56249b0a8f68c1c7 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:37:00 -0400 Subject: [PATCH 22/74] implemented key codes --- switchbotKeypadTouch | 74 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 8c68e06..1803d78 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -22,6 +22,8 @@ Change history: */ +import groovy.json.JsonBuilder + @Field static final String PERMANENT = "permanent" @Field static final String TIMED = "timeLimit" @Field static final String DISPOSABLE = "disposable" @@ -31,11 +33,15 @@ Change history: @Field static final String FAILED = "failed" @Field static final String TIMEOUT = "timeout" +@Field static final String PASSCODE_NORMAL = "normal" +@Field static final String PASSCODE_EXPIRED = "expired" + metadata { definition(name: "SwitchBot Keypad Touch", namespace: "tomw", author: "dsegall", importUrl: "") { capability "Refresh" + capability "LockCodes" command "createKey", [[name: "ID*", type: "STRING"], [name: "Password*", type: "STRING"], [name: "Type", type: "ENUM", constraints: [PERMANENT, TIMED, DISPOSABLE, EMERGENCY]], [name: "Start Time", type: "NUMBER"], [name: "End Time", type: "NUMBER"]] command "deleteKey", [[name: "ID*", type: "STRING"]] @@ -60,6 +66,10 @@ def logDebug(msg) def initialize() { + atomicState.numDeletions = 0 + atomicState.needsReread = false + sendEvent(name: "codeLength", value: 12) + sendEvent(name: "maxCodes", value: 100) refresh() } @@ -70,7 +80,24 @@ def refresh() def parse(body) { - logDebug body + if (logEnable) + logDebug body + + if (atomicState.numDeletions > 0) atomicState.numDeletions-- + + if (body.result == "success") { + atomicState.needsReread = true + if (body.eventName == "createKey") + sendEvent(name: "codeChanged", value: "added") + else if (body.eventName == "deleteKey") + sendEvent(name: "codeChanged", value: "deleted") + + if (atomicState.numDeletions == 0) { + atomicState.needsReread = false + parent?.rereadDevices() + } + } + else sendEvent(name: "codeChanged", value: "failed") } def writeDeviceCommand(command, parameter = "default", commandType = "command") @@ -87,9 +114,50 @@ def createKey(String name, String code, String type=PERMANENT, long startTime=0, writeDeviceCommand("createKey", params) } -def deleteKey(String name) { - writeDeviceCommand("deleteKey", ["name": name]) +def deleteKey(String id) { + writeDeviceCommand("deleteKey", ["id": id]) } def readDeviceDetails(details) { + atomicState.lockCodes = [] + def expiredCodes = [] + for (it in details?.keyList) { + atomicState.lockCodes << ["id": it.id, "name": it.name, "code": it.password, "iv": it.iv] + if (it.status == PASSCODE_EXPIRED) + expiredCodes << it.id + } + + sendEvent(name: "lockCodes", value: getCodes()) + + if (!expiredCodes.isEmpty()) { + cleanExpiredCodes(expiredCodes) + } +} + +def cleanExpiredCodes(expiredCodes) { + atomicState.numDeletions = expiredCodes.size() + for (code in expiredCodes) deleteKey(code) +} + +def deleteCode(position) { + if (position < atomicState.lockCodes.size() && atomicState.lockCodes[position]) + deleteKey(atomicState.lockCodes[position].id) +} + +def setCodeLength() { + // Not supported +} + +def setCode(codeposition, pincode, name) { + deleteCode(codeposition) + createKey(name, pincode) +} + +def getCodes() { + def codes = [] + for (it in atomicState.lockCodes) { + codes << ["id": it.id, "name": it.name, "code": parent?.decryptPasscode(it.iv, it.code)] + } + + return new JsonBuilder(codes).toString() } From db09fd394e35561b5af472937498a2203f35624d Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:40:58 -0400 Subject: [PATCH 23/74] added requestor to rereadDevices --- switchbotSystem | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index b75d151..7d93215 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -97,12 +97,12 @@ def initialize() } } -def rereadDevices() +def rereadDevices(requestor=null) { try { readDevices() - createChildDevices(false) + createChildDevices(false, requestor) } catch (Exception e) { @@ -208,7 +208,7 @@ def refreshFromParent(child) } } -def createChildDevices(boolean createNewDevices=true) +def createChildDevices(boolean createNewDevices=true, Object requestor=null) { def devices = getDevices() @@ -253,7 +253,7 @@ def createChildDevices(boolean createNewDevices=true) childDevice = createChildDevice(it.deviceName, it.deviceId, it.deviceType) } - if (childDevice) + if (childDevice && (requestor == null || requestor == childDevice)) childDevice.readDeviceDetails(it) } } From fd6fba159441f39b76fedfbd1d2d3f7bbd113648 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:41:43 -0400 Subject: [PATCH 24/74] pass in this device as the requestor to rereadDevices --- switchbotKeypadTouch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 1803d78..07f9cca 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -94,7 +94,7 @@ def parse(body) if (atomicState.numDeletions == 0) { atomicState.needsReread = false - parent?.rereadDevices() + parent?.rereadDevices(this) } } else sendEvent(name: "codeChanged", value: "failed") From aed1e908fe52624b858ccb97ba1d1daf74605dc9 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:43:24 -0400 Subject: [PATCH 25/74] use DNI --- switchbotSystem | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index 7d93215..d2a1d4c 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -97,12 +97,12 @@ def initialize() } } -def rereadDevices(requestor=null) +def rereadDevices(String requestorDNI=null) { try { readDevices() - createChildDevices(false, requestor) + createChildDevices(false, requestorDNI) } catch (Exception e) { @@ -208,7 +208,7 @@ def refreshFromParent(child) } } -def createChildDevices(boolean createNewDevices=true, Object requestor=null) +def createChildDevices(boolean createNewDevices=true, String requestorDNI=null) { def devices = getDevices() @@ -253,7 +253,7 @@ def createChildDevices(boolean createNewDevices=true, Object requestor=null) childDevice = createChildDevice(it.deviceName, it.deviceId, it.deviceType) } - if (childDevice && (requestor == null || requestor == childDevice)) + if (childDevice && (requestorDNI == null || requestorDNI == childDevice.getDeviceNetworkId())) childDevice.readDeviceDetails(it) } } From a6351b856392f9398746608e1ba5ec5375b6c42a Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:43:41 -0400 Subject: [PATCH 26/74] use DNI --- switchbotKeypadTouch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 07f9cca..efcb10f 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -94,7 +94,7 @@ def parse(body) if (atomicState.numDeletions == 0) { atomicState.needsReread = false - parent?.rereadDevices(this) + parent?.rereadDevices(device.getDeviceNetworkId()) } } else sendEvent(name: "codeChanged", value: "failed") From bbe163927916a5864c938c3ff97fefb64f27c3c7 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:52:22 -0400 Subject: [PATCH 27/74] implement LockCodes capability --- switchbotKeypadTouch | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index efcb10f..7542e95 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -66,7 +66,7 @@ def logDebug(msg) def initialize() { - atomicState.numDeletions = 0 + atomicState.pendingCommands = 0 atomicState.needsReread = false sendEvent(name: "codeLength", value: 12) sendEvent(name: "maxCodes", value: 100) @@ -75,7 +75,7 @@ def initialize() def refresh() { - parent?.refreshFromParent(device) + parent?.rereadDevices(device.getDeviceNetworkId()) } def parse(body) @@ -83,7 +83,7 @@ def parse(body) if (logEnable) logDebug body - if (atomicState.numDeletions > 0) atomicState.numDeletions-- + if (atomicState.pendingCommands > 0) atomicState.pendingCommands-- if (body.result == "success") { atomicState.needsReread = true @@ -92,7 +92,7 @@ def parse(body) else if (body.eventName == "deleteKey") sendEvent(name: "codeChanged", value: "deleted") - if (atomicState.numDeletions == 0) { + if (atomicState.pendingCommands == 0) { atomicState.needsReread = false parent?.rereadDevices(device.getDeviceNetworkId()) } @@ -111,10 +111,14 @@ def writeDeviceCommand(command, parameter = "default", commandType = "command") def createKey(String name, String code, String type=PERMANENT, long startTime=0, long endTime=0) { Map params = ["name": name, "type": type, "password": code, "startTime": startTime, "endTime": endTime] + atomicState.pendingCommands++ writeDeviceCommand("createKey", params) } -def deleteKey(String id) { +def deleteKey(String id, boolean recordPendingCommand=true) { + if (recordPendingCommand) + atomicState.pendingCommands++ + writeDeviceCommand("deleteKey", ["id": id]) } @@ -135,8 +139,8 @@ def readDeviceDetails(details) { } def cleanExpiredCodes(expiredCodes) { - atomicState.numDeletions = expiredCodes.size() - for (code in expiredCodes) deleteKey(code) + atomicState.pendingCommands += expiredCodes.size() + for (code in expiredCodes) deleteKey(code, false) } def deleteCode(position) { From 8461b5f3cb3afa19cd96b363e757d07dd435113d Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Mon, 25 Mar 2024 07:43:07 -0400 Subject: [PATCH 28/74] added bounds check --- switchbotKeypadTouch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 7542e95..a1dff52 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -144,7 +144,7 @@ def cleanExpiredCodes(expiredCodes) { } def deleteCode(position) { - if (position < atomicState.lockCodes.size() && atomicState.lockCodes[position]) + if (position >= 0 && position < atomicState.lockCodes.size() && atomicState.lockCodes[position]) deleteKey(atomicState.lockCodes[position].id) } From 7b8fee667030a2a6e8c157bcb7541b052a34e0de Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Mon, 25 Mar 2024 07:45:45 -0400 Subject: [PATCH 29/74] added map of codes by name --- switchbotKeypadTouch | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index a1dff52..bb70a7c 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -45,6 +45,7 @@ metadata command "createKey", [[name: "ID*", type: "STRING"], [name: "Password*", type: "STRING"], [name: "Type", type: "ENUM", constraints: [PERMANENT, TIMED, DISPOSABLE, EMERGENCY]], [name: "Start Time", type: "NUMBER"], [name: "End Time", type: "NUMBER"]] command "deleteKey", [[name: "ID*", type: "STRING"]] + command "deleteKeyByName", [[name: "Name*", type: "STRING"]] } preferences @@ -122,11 +123,20 @@ def deleteKey(String id, boolean recordPendingCommand=true) { writeDeviceCommand("deleteKey", ["id": id]) } +def deleteKeyByName(String name, boolean recordPendingCommand=true) { + String id = atomicState.lockCodesByName[name] + if (id) { + deleteKey(id, recordPendingCommand) + } +} + def readDeviceDetails(details) { atomicState.lockCodes = [] + atomimcState.lockCodesByName = [:] def expiredCodes = [] for (it in details?.keyList) { atomicState.lockCodes << ["id": it.id, "name": it.name, "code": it.password, "iv": it.iv] + atomicState.lockCodesByName[it.name] = it.id if (it.status == PASSCODE_EXPIRED) expiredCodes << it.id } From 52abb71debc5746cb6a7ea5e77c554bd94af6ff4 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:21:31 -0400 Subject: [PATCH 30/74] Hub mini 2 and smart lock pro --- switchbotSystem | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index d2a1d4c..5976a41 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -315,12 +315,12 @@ def createChildDevice(name, id, deviceType) { try { - def customDevTypes = ["Bot", "Curtain", "Meter", "IR Device", "Humidifier", "Strip Light", "Smart Lock", "Motion Sensor", "Contact Sensor", "Plug Mini", "Blind Tilt", "Keypad Touch"] + def customDevTypes = ["Bot", "Curtain", "Meter", "IR Device", "Humidifier", "Strip Light", "Smart Lock", "Motion Sensor", "Contact Sensor", "Plug Mini", "Blind Tilt", "Keypad Touch", "Smart Lock Pro"] def genericDevTypes = [] deviceType = deviceType.toString() - if(["Hub Mini"].contains(deviceType)) + if(["Hub Mini", "Hub Mini2"].contains(deviceType)) { // unsupported child type, so bail out return From 0bda7b8e732cbe05660bbc8998ea05f5e69c4f4e Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:23:05 -0400 Subject: [PATCH 31/74] smart lock pro --- switchbotSystem | 2 ++ 1 file changed, 2 insertions(+) diff --git a/switchbotSystem b/switchbotSystem index 5976a41..8d662ab 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -331,6 +331,8 @@ def createChildDevice(name, id, deviceType) logDebug("createChildDevice: deviceType not supported") throw new Exception("deviceType not supported") } + + if (deviceType == "Smart Lock Pro") deviceType = "Smart Lock" def virtDevType = ((customDevTypes.contains(deviceType)) ? "SwitchBot " : "Generic Component ") + deviceType def virtDevData = [name: childName(name, deviceType), label: childName(name, deviceType), isComponent: false] From 126389d29f0c8c478ce1f135e91a0da8fa70df3f Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:36:01 -0400 Subject: [PATCH 32/74] smart lock pro --- switchbotEventsApp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/switchbotEventsApp b/switchbotEventsApp index 2345c93..b8fbf36 100644 --- a/switchbotEventsApp +++ b/switchbotEventsApp @@ -165,7 +165,13 @@ def processEvent(eventMap) // check whether we know about this type of device def devTypeInfo = deviceTypesMap.getAt(eventMap.context.deviceType) - if(!devTypeInfo) { return } + if(!devTypeInfo) { + if (context.lockState) { + // This is a Smart Lock Pro which doesn't include device type in the message + devTypeInfo = "Smart Lock" + } + else return + } // build the event that we'll pass to the driver def newEvent = [:] From e2e4d2b504b9c4145dc34cdcc246152582a79c81 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:44:02 -0400 Subject: [PATCH 33/74] Smart Lock Pro doesn't send a device type in its updates --- switchbotEventsApp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/switchbotEventsApp b/switchbotEventsApp index b8fbf36..c5c4ae1 100644 --- a/switchbotEventsApp +++ b/switchbotEventsApp @@ -166,9 +166,10 @@ def processEvent(eventMap) // check whether we know about this type of device def devTypeInfo = deviceTypesMap.getAt(eventMap.context.deviceType) if(!devTypeInfo) { - if (context.lockState) { + if (eventMap.context.lockState) { // This is a Smart Lock Pro which doesn't include device type in the message - devTypeInfo = "Smart Lock" + devTypeInfo = deviceTypesMap.getAt("WoLock") + logDebug("Received event with no device type, mapping to Smart Lock") } else return } @@ -232,5 +233,3 @@ static @Field motionSensorAttrsMap = [ detectionState: "moveDetected" ] - - From f5ad9c88ad63f2f895c0d1320f8847802c208dcd Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:46:49 -0400 Subject: [PATCH 34/74] Added battery level --- switchbotSmartLock | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/switchbotSmartLock b/switchbotSmartLock index fa0021a..0f54fe6 100644 --- a/switchbotSmartLock +++ b/switchbotSmartLock @@ -30,6 +30,7 @@ metadata capability "ContactSensor" capability "Lock" capability "Refresh" + capability "Battery" } } @@ -61,6 +62,12 @@ def parse(body) def lState = body.lockState.toLowerCase() sendEvent(name: "lock", value: ["unlocked", "locked", "jammed"].contains(lState) ? lState : "unknown") } + + if(null != body.battery) + { + def battery = body.battery.toInteger() + sendEvent(name: "battery", value: battery) + } } def writeDeviceCommand(command, parameter = "default", commandType = "command") From 7a10f65dabcab6e8fa6db3f505e2b77a7bcf5c6c Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 19:07:41 -0400 Subject: [PATCH 35/74] added missing import --- switchbotKeypadTouch | 1 + 1 file changed, 1 insertion(+) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index bb70a7c..d3f2121 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -23,6 +23,7 @@ Change history: */ import groovy.json.JsonBuilder +import groovy.transform.Field @Field static final String PERMANENT = "permanent" @Field static final String TIMED = "timeLimit" From eda772545a081e3e3759eb3f3ee16295eca9ff1f Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 19:08:47 -0400 Subject: [PATCH 36/74] fixed typo --- switchbotKeypadTouch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index d3f2121..09aa8e1 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -133,7 +133,7 @@ def deleteKeyByName(String name, boolean recordPendingCommand=true) { def readDeviceDetails(details) { atomicState.lockCodes = [] - atomimcState.lockCodesByName = [:] + atomicState.lockCodesByName = [:] def expiredCodes = [] for (it in details?.keyList) { atomicState.lockCodes << ["id": it.id, "name": it.name, "code": it.password, "iv": it.iv] From 3676e4deb79dfe8e80c132e439c0ec2ca4d80c97 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:04:06 -0400 Subject: [PATCH 37/74] fixed lock code decryption --- switchbotSystem | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index 8d662ab..b1ca2be 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -36,6 +36,10 @@ Change history: */ +import javax.crypto.* +import javax.crypto.spec.* +import org.apache.commons.codec.binary.Base64 + metadata { definition(name: "SwitchBot System", namespace: "tomw", author: "tomw", importUrl: "") @@ -531,21 +535,32 @@ def hmac_sha256(String secretKey, String data) def decryptPasscode(String iv, String encrypted) { try { - def ivSpec = new javax.crypto.spec.IvParameterSpec(iv.getBytes("UTF-8")) - def skeySpec = new javax.crypto.spec.SecretKeySpec(secretKey.getBytes("UTF-8"), "AES") - - def cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5PADDING") + byte[] ivBytes = hexStringToByteArray(iv) + def ivSpec = new IvParameterSpec(ivBytes) + def skeySpec = new SecretKeySpec(hexStringToByteArray(secretKey), "AES") + + def cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec) - byte[] original = cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(encrypted)) + byte[] original = cipher.doFinal(encrypted.decodeBase64()) return new String(original) } catch (Exception ex) { - ex.printStackTrace() + logDebug(ex) } return null } +byte[] hexStringToByteArray(String s) { + int len = s.length() + byte[] data = new byte[len / 2] + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)) + } + return data +} + def httpGetExec(params, throwToCaller = false) { logDebug("httpGetExec(${params})") From b7ea4310dbb4956c12f9f16b2a968eff9f1acfe1 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:04:35 -0400 Subject: [PATCH 38/74] debug logging --- switchbotKeypadTouch | 3 +++ 1 file changed, 3 insertions(+) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 09aa8e1..8d100f6 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -43,6 +43,7 @@ metadata { capability "Refresh" capability "LockCodes" + capability "Initialize" command "createKey", [[name: "ID*", type: "STRING"], [name: "Password*", type: "STRING"], [name: "Type", type: "ENUM", constraints: [PERMANENT, TIMED, DISPOSABLE, EMERGENCY]], [name: "Start Time", type: "NUMBER"], [name: "End Time", type: "NUMBER"]] command "deleteKey", [[name: "ID*", type: "STRING"]] @@ -135,7 +136,9 @@ def readDeviceDetails(details) { atomicState.lockCodes = [] atomicState.lockCodesByName = [:] def expiredCodes = [] + logDebug("Details=" + details) for (it in details?.keyList) { + logDebug("Code=" + it) atomicState.lockCodes << ["id": it.id, "name": it.name, "code": it.password, "iv": it.iv] atomicState.lockCodesByName[it.name] = it.id if (it.status == PASSCODE_EXPIRED) From fd28fd90c8e94cdeac3bf6c4e5991bbdf571a25a Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:17:15 -0400 Subject: [PATCH 39/74] fixed atomicstate usage --- switchbotKeypadTouch | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 8d100f6..e25ed11 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -127,6 +127,7 @@ def deleteKey(String id, boolean recordPendingCommand=true) { def deleteKeyByName(String name, boolean recordPendingCommand=true) { String id = atomicState.lockCodesByName[name] + logDebug("delete KeyByName found key ${id}") if (id) { deleteKey(id, recordPendingCommand) } @@ -136,15 +137,22 @@ def readDeviceDetails(details) { atomicState.lockCodes = [] atomicState.lockCodesByName = [:] def expiredCodes = [] + + def newCodes = [] + def newCodesByName = [:] + logDebug("Details=" + details) for (it in details?.keyList) { logDebug("Code=" + it) - atomicState.lockCodes << ["id": it.id, "name": it.name, "code": it.password, "iv": it.iv] - atomicState.lockCodesByName[it.name] = it.id + newCodes << ["id": it.id, "name": it.name, "code": it.password, "iv": it.iv] + newCodesByName[it.name] = it.id if (it.status == PASSCODE_EXPIRED) expiredCodes << it.id } + atomicState.lockCodes = newCodes + atomicState.lockCodesByName = newCodesByName + sendEvent(name: "lockCodes", value: getCodes()) if (!expiredCodes.isEmpty()) { From f587cf7e8fad6e06bb073a21c767dcbe4dfa994e Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:40:15 -0400 Subject: [PATCH 40/74] logging --- switchbotKeypadTouch | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index e25ed11..e3d6fdc 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -84,7 +84,7 @@ def refresh() def parse(body) { if (logEnable) - logDebug body + logDebug("parse: ${body}") if (atomicState.pendingCommands > 0) atomicState.pendingCommands-- @@ -97,7 +97,8 @@ def parse(body) if (atomicState.pendingCommands == 0) { atomicState.needsReread = false - parent?.rereadDevices(device.getDeviceNetworkId()) + logDebug("Refreshing...") + runIn(1, "refresh") } } else sendEvent(name: "codeChanged", value: "failed") From cd94af19adf182e32022616fe34efe85d51336ed Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:40:30 -0400 Subject: [PATCH 41/74] logging --- switchbotEventsApp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/switchbotEventsApp b/switchbotEventsApp index c5c4ae1..abba89a 100644 --- a/switchbotEventsApp +++ b/switchbotEventsApp @@ -157,10 +157,10 @@ def observeEvent() def processEvent(eventMap) { - if(!eventMap) { return } - logDebug("incoming event: ${eventMap}") + if(!eventMap) { return } + if("changeReport" != eventMap?.eventType) { return } // check whether we know about this type of device @@ -233,3 +233,5 @@ static @Field motionSensorAttrsMap = [ detectionState: "moveDetected" ] + + From 14fcfde8f412cb4af1c76bc712b1ca9fd92dc81a Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:40:59 -0400 Subject: [PATCH 42/74] logging From 4965a0235a5a224b083930f7c7b5b946c1004177 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Fri, 5 Apr 2024 09:42:27 -0400 Subject: [PATCH 43/74] more user friendly support for timed keys --- switchbotKeypadTouch | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index e3d6fdc..1aa2bc4 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -25,6 +25,8 @@ Change history: import groovy.json.JsonBuilder import groovy.transform.Field +import java.text.SimpleDateFormat + @Field static final String PERMANENT = "permanent" @Field static final String TIMED = "timeLimit" @Field static final String DISPOSABLE = "disposable" @@ -45,7 +47,7 @@ metadata capability "LockCodes" capability "Initialize" - command "createKey", [[name: "ID*", type: "STRING"], [name: "Password*", type: "STRING"], [name: "Type", type: "ENUM", constraints: [PERMANENT, TIMED, DISPOSABLE, EMERGENCY]], [name: "Start Time", type: "NUMBER"], [name: "End Time", type: "NUMBER"]] + command "createKey", [[name: "ID*", type: "STRING"], [name: "Password*", type: "STRING"], [name: "Type", type: "ENUM", constraints: [PERMANENT, TIMED, DISPOSABLE, EMERGENCY]], [name: "Start Time", type: "STRING"], [name: "End Time", type: "STRING"]] command "deleteKey", [[name: "ID*", type: "STRING"]] command "deleteKeyByName", [[name: "Name*", type: "STRING"]] } @@ -113,7 +115,14 @@ def writeDeviceCommand(command, parameter = "default", commandType = "command") } } -def createKey(String name, String code, String type=PERMANENT, long startTime=0, long endTime=0) { +def createKey(String name, String code, String type=PERMANENT, String startTimeS=null, String endTimeS=null) { + SimpleDateFormat df = new SimpleDateFormat("HH:mm") + Date date = startTimeS ? df.parse(startTimeS) : null + long startTime = date ? date.getTime() : 0 + + date = endTimeS ? df.parse(endTimeS) : null + long endTime = date ? date.getTime() : 0 + Map params = ["name": name, "type": type, "password": code, "startTime": startTime, "endTime": endTime] atomicState.pendingCommands++ writeDeviceCommand("createKey", params) From a2dea54bb2b4870e8260d47a54c02e30be3b8e49 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Fri, 5 Apr 2024 09:45:08 -0400 Subject: [PATCH 44/74] changed label on createKey --- switchbotKeypadTouch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 1aa2bc4..73bbbed 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -47,7 +47,7 @@ metadata capability "LockCodes" capability "Initialize" - command "createKey", [[name: "ID*", type: "STRING"], [name: "Password*", type: "STRING"], [name: "Type", type: "ENUM", constraints: [PERMANENT, TIMED, DISPOSABLE, EMERGENCY]], [name: "Start Time", type: "STRING"], [name: "End Time", type: "STRING"]] + command "createKey", [[name: "Name*", type: "STRING"], [name: "Password*", type: "STRING"], [name: "Type", type: "ENUM", constraints: [PERMANENT, TIMED, DISPOSABLE, EMERGENCY]], [name: "Start Time", type: "STRING"], [name: "End Time", type: "STRING"]] command "deleteKey", [[name: "ID*", type: "STRING"]] command "deleteKeyByName", [[name: "Name*", type: "STRING"]] } From e3ac8ea0cd15b0fbc9f3be9ed6fa7ceaa365b585 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Fri, 5 Apr 2024 11:20:49 -0400 Subject: [PATCH 45/74] lock code encryption --- switchbotKeypadTouch | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 73bbbed..79a502d 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -57,6 +57,7 @@ metadata section { input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: false + input name: "lockCodeEncryption", type: "bool", title: "Enable lock code encryption", defaultValue: false } } } @@ -78,6 +79,10 @@ def initialize() refresh() } +def updated() { + updateCodesAttr() +} + def refresh() { parent?.rereadDevices(device.getDeviceNetworkId()) @@ -163,13 +168,17 @@ def readDeviceDetails(details) { atomicState.lockCodes = newCodes atomicState.lockCodesByName = newCodesByName - sendEvent(name: "lockCodes", value: getCodes()) + updateCodesAttr() if (!expiredCodes.isEmpty()) { cleanExpiredCodes(expiredCodes) } } +def updateCodesAttr() { + sendEvent(name: "lockCodes", value: getCodes(lockCodeEncryption == null ? false : lockCodeEncryption)) +} + def cleanExpiredCodes(expiredCodes) { atomicState.pendingCommands += expiredCodes.size() for (code in expiredCodes) deleteKey(code, false) @@ -189,11 +198,12 @@ def setCode(codeposition, pincode, name) { createKey(name, pincode) } -def getCodes() { +def getCodes(boolean encryptionEnabled=true) { def codes = [] for (it in atomicState.lockCodes) { codes << ["id": it.id, "name": it.name, "code": parent?.decryptPasscode(it.iv, it.code)] } - return new JsonBuilder(codes).toString() + String str = new JsonBuilder(codes).toString() + return encryptionEnabled ? encrypt(str) : str } From 570caf500517f11aa7d49b2d757de27d29d6a152 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Fri, 5 Apr 2024 11:58:38 -0400 Subject: [PATCH 46/74] fixed lock code manager json --- switchbotKeypadTouch | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 79a502d..03dc70b 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -199,9 +199,10 @@ def setCode(codeposition, pincode, name) { } def getCodes(boolean encryptionEnabled=true) { - def codes = [] + def codes = [:] + int i = 0 for (it in atomicState.lockCodes) { - codes << ["id": it.id, "name": it.name, "code": parent?.decryptPasscode(it.iv, it.code)] + codes[i++] = ["name": it.name, "code": parent?.decryptPasscode(it.iv, it.code)] } String str = new JsonBuilder(codes).toString() From c4a298b9fd402c1c56b2ef3a41a57fb55066ba33 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Fri, 5 Apr 2024 12:01:09 -0400 Subject: [PATCH 47/74] lock code encryption --- switchbotKeypadTouch | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 03dc70b..562105a 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -176,7 +176,7 @@ def readDeviceDetails(details) { } def updateCodesAttr() { - sendEvent(name: "lockCodes", value: getCodes(lockCodeEncryption == null ? false : lockCodeEncryption)) + sendEvent(name: "lockCodes", value: getCodes()) } def cleanExpiredCodes(expiredCodes) { @@ -198,7 +198,8 @@ def setCode(codeposition, pincode, name) { createKey(name, pincode) } -def getCodes(boolean encryptionEnabled=true) { +def getCodes() { + boolean encryptionEnabled=lockCodeEncryption == null ? false : lockCodeEncryption def codes = [:] int i = 0 for (it in atomicState.lockCodes) { From 7ede50572b54fd86841940d1cb3a1b1f3093fe2d Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 5 May 2024 09:13:00 -0400 Subject: [PATCH 48/74] Made auto delete expired codes optional --- switchbotKeypadTouch | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 562105a..cb335e1 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -58,6 +58,7 @@ metadata { input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: false input name: "lockCodeEncryption", type: "bool", title: "Enable lock code encryption", defaultValue: false + input name: "deleteExpiredCodes", type: "bool", title: "Automatically delete expired codes", defaultValue: true } } } @@ -137,12 +138,13 @@ def deleteKey(String id, boolean recordPendingCommand=true) { if (recordPendingCommand) atomicState.pendingCommands++ - writeDeviceCommand("deleteKey", ["id": id]) + logDebug("deleteKey id=${id}") + writeDeviceCommand("deleteKey", ["id": id]) } def deleteKeyByName(String name, boolean recordPendingCommand=true) { String id = atomicState.lockCodesByName[name] - logDebug("delete KeyByName found key ${id}") + logDebug("deleteKeyByName found key ${id}") if (id) { deleteKey(id, recordPendingCommand) } @@ -180,13 +182,15 @@ def updateCodesAttr() { } def cleanExpiredCodes(expiredCodes) { - atomicState.pendingCommands += expiredCodes.size() - for (code in expiredCodes) deleteKey(code, false) + if (deleteExpiredCodes == null || deleteExpiredCodes) { + atomicState.pendingCommands += expiredCodes.size() + for (code in expiredCodes) deleteKey(code.toString(), false) + } } def deleteCode(position) { if (position >= 0 && position < atomicState.lockCodes.size() && atomicState.lockCodes[position]) - deleteKey(atomicState.lockCodes[position].id) + deleteKey(atomicState.lockCodes[position].id.toString()) } def setCodeLength() { From 4edc9b6d5ac931f66aa720036286fb7a98ca3a49 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 5 May 2024 09:14:59 -0400 Subject: [PATCH 49/74] Save code status in state --- switchbotKeypadTouch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index cb335e1..e902853 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -161,7 +161,7 @@ def readDeviceDetails(details) { logDebug("Details=" + details) for (it in details?.keyList) { logDebug("Code=" + it) - newCodes << ["id": it.id, "name": it.name, "code": it.password, "iv": it.iv] + newCodes << ["id": it.id, "name": it.name, "code": it.password, "iv": it.iv, "status": it.status] newCodesByName[it.name] = it.id if (it.status == PASSCODE_EXPIRED) expiredCodes << it.id From f81e8a9d9ca47173341a790660f5298b6f4ad555 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 5 May 2024 09:20:02 -0400 Subject: [PATCH 50/74] Added debug logging --- switchbotKeypadTouch | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index e902853..4034c3c 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -172,6 +172,7 @@ def readDeviceDetails(details) { updateCodesAttr() + logDebug("Found ${expiredCodes.size()} expired codes") if (!expiredCodes.isEmpty()) { cleanExpiredCodes(expiredCodes) } @@ -182,9 +183,15 @@ def updateCodesAttr() { } def cleanExpiredCodes(expiredCodes) { - if (deleteExpiredCodes == null || deleteExpiredCodes) { + boolean clean = deleteExpiredCodes == null ? true : deleteExpiredCodes + logDebug("cleanExpiredCodes=${clean} size=${expiredCodes.size}") + if (clean) { atomicState.pendingCommands += expiredCodes.size() - for (code in expiredCodes) deleteKey(code.toString(), false) + for (code in expiredCodes) { + String codeStr = code.toString() + logDebug("Deleting expired code ${codeStr}") + deleteKey(codeStr, false) + } } } From 220cd34ea45f6ced4ecc64807633128b91662fb2 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 5 May 2024 09:57:55 -0400 Subject: [PATCH 51/74] don't allow creating timed codes effective in the past --- switchbotKeypadTouch | 49 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 4034c3c..83cb3d4 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -25,7 +25,7 @@ Change history: import groovy.json.JsonBuilder import groovy.transform.Field -import java.text.SimpleDateFormat +import java.util.Calendar @Field static final String PERMANENT = "permanent" @Field static final String TIMED = "timeLimit" @@ -122,16 +122,47 @@ def writeDeviceCommand(command, parameter = "default", commandType = "command") } def createKey(String name, String code, String type=PERMANENT, String startTimeS=null, String endTimeS=null) { - SimpleDateFormat df = new SimpleDateFormat("HH:mm") - Date date = startTimeS ? df.parse(startTimeS) : null - long startTime = date ? date.getTime() : 0 + if (!atomicState.lockCodesByName.containsKey(name)) { + long startTime = 0 + long endTime = 0 + Date now = new Date() + + if (type == TIMED && !startTimeS) { + startTime = now.getTime() + 5000 + } + + if (startTimeS) { + Calendar cal = timeToday(startTimeS) + Date date = cal.getTime() + startTime = date.getTime() + if (startTime < now.getTime()) + startTime = now.getTime() + 5000 + } + + if (type == TIMED && !endTimeS) { + endTimeS = "23:59" + } - date = endTimeS ? df.parse(endTimeS) : null - long endTime = date ? date.getTime() : 0 + if (endTimeS) { + Calendar cal = timeToday(endTimeS) + Date date = cal.getTime() + endTime = date.getTime() + } - Map params = ["name": name, "type": type, "password": code, "startTime": startTime, "endTime": endTime] - atomicState.pendingCommands++ - writeDeviceCommand("createKey", params) + Map params = ["name": name, "type": type, "password": code, "startTime": startTime, "endTime": endTime] + atomicState.pendingCommands++ + writeDeviceCommand("createKey", params) + } +} + +Calendar timeToday(String time) { + Calendar today = Calendar.getInstance() + today.set(Calendar.SECOND, 0) + today.set(Calendar.MILLISECOND, 0) + String[] parts = time.split(":") + today.set(Calendar.HOUR_OF_DAY, parts[0] as int) + today.set(Calendar.MINUTE, parts[1] as int) + return today } def deleteKey(String id, boolean recordPendingCommand=true) { From d57b94976cf55c221f7d9408be9f417fc761d39e Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 5 May 2024 10:02:32 -0400 Subject: [PATCH 52/74] exception handling --- switchbotKeypadTouch | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 83cb3d4..8835ad0 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -151,7 +151,13 @@ def createKey(String name, String code, String type=PERMANENT, String startTimeS Map params = ["name": name, "type": type, "password": code, "startTime": startTime, "endTime": endTime] atomicState.pendingCommands++ - writeDeviceCommand("createKey", params) + try { + writeDeviceCommand("createKey", params) + } + catch (Exception e) { + logDebug("createKey failed: ${e.getMessage()}") + atomicState.pendingCommands-- + } } } @@ -170,7 +176,13 @@ def deleteKey(String id, boolean recordPendingCommand=true) { atomicState.pendingCommands++ logDebug("deleteKey id=${id}") - writeDeviceCommand("deleteKey", ["id": id]) + try { + writeDeviceCommand("deleteKey", ["id": id]) + } + catch (Exception e) { + logDebug("deleteKey failed: ${e.getMessage()}") + atomicState.pendingCommands-- + } } def deleteKeyByName(String name, boolean recordPendingCommand=true) { From 5c001c63713d867d904805854ba4820e254243ae Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Tue, 7 May 2024 11:19:10 -0400 Subject: [PATCH 53/74] added lock pro device type --- switchbotEventsApp | 1 + 1 file changed, 1 insertion(+) diff --git a/switchbotEventsApp b/switchbotEventsApp index abba89a..55a8d85 100644 --- a/switchbotEventsApp +++ b/switchbotEventsApp @@ -203,6 +203,7 @@ import groovy.transform.Field [ WoContact: [name: "Contact Sensor", attrs: contactSensorAttrsMap], WoLock: [name: "Smart Lock"], + WoLockPro: [name: "Smart Lock"] WoMeter: [name: "Meter"], WoMeterPlus: [name: "Meter"], WoHub2: [name: "Meter"], From 69db597aaf36461cedd7d092adfb0b1dda4ede20 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 9 May 2024 20:39:15 -0400 Subject: [PATCH 54/74] Added battery capability --- switchbotMeter | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/switchbotMeter b/switchbotMeter index fd07c96..7aafa5c 100644 --- a/switchbotMeter +++ b/switchbotMeter @@ -18,6 +18,7 @@ limitations under the License. Change history: +0.9.14 - dsegall - Added Battery capability 0.9.13 - tomw - Added Contact Sensor and Motion Sensor support. 0.9.12 - tomw - Added Smart Lock and webhook events support. 0.9.0 - tomw - Initial release. @@ -32,6 +33,7 @@ metadata capability "RelativeHumidityMeasurement" capability "Refresh" capability "TemperatureMeasurement" + capability "Battery" } } @@ -70,6 +72,11 @@ def parse(body) def tempVal = degC ? body.temperature.toDouble() : celsiusToFahrenheit(body.temperature.toDouble()) sendEvent(name: "temperature", value: tempVal?.toDouble()) } + if(null != body.battery) + { + def battery = body.battery.toInteger() + sendEvent(name: "battery", value: battery) + } } def readDeviceDetails(details) { From 11550c135ee614e4ec0955630ab71917ef59c121 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Thu, 9 May 2024 23:14:05 -0400 Subject: [PATCH 55/74] fixes for create key enforce 1hr minimum; make sure end time is after start time --- switchbotKeypadTouch | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 8835ad0..7a1d402 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -39,6 +39,9 @@ import java.util.Calendar @Field static final String PASSCODE_NORMAL = "normal" @Field static final String PASSCODE_EXPIRED = "expired" +@Field static final MILLISECONDS_IN_DAY = 86400000 +@Field static final MILLISECONDS_IN_HOUR = 3600000 + metadata { definition(name: "SwitchBot Keypad Touch", namespace: "tomw", author: "dsegall", importUrl: "") @@ -123,6 +126,7 @@ def writeDeviceCommand(command, parameter = "default", commandType = "command") def createKey(String name, String code, String type=PERMANENT, String startTimeS=null, String endTimeS=null) { if (!atomicState.lockCodesByName.containsKey(name)) { + logDebug("createKey ${name}") long startTime = 0 long endTime = 0 Date now = new Date() @@ -147,6 +151,13 @@ def createKey(String name, String code, String type=PERMANENT, String startTimeS Calendar cal = timeToday(endTimeS) Date date = cal.getTime() endTime = date.getTime() + if (now.after(date)) { + if (startTimeS) startTime += MILLISECONDS_IN_DAY + endTime += MILLISECONDS_IN_DAY + } + + if (startTimeS && endTime - startTime < MILLISECONDS_IN_HOUR) + endTime = startTime + MILLISECONDS_IN_HOUR + 60000 } Map params = ["name": name, "type": type, "password": code, "startTime": startTime, "endTime": endTime] From a752a3c702cc483eb24ff85ef6f6b5e158f40af0 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:31:53 -0400 Subject: [PATCH 56/74] Update switchbotBlindTilt --- switchbotBlindTilt | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotBlindTilt b/switchbotBlindTilt index 4cb2f06..4e21625 100644 --- a/switchbotBlindTilt +++ b/switchbotBlindTilt @@ -247,6 +247,3 @@ def correctedPositions(rawPos) return retVal } - -def readDeviceDetails(details) { -} From c6834c8abfb130b6af340b132d0abc2c18ff442a Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:32:04 -0400 Subject: [PATCH 57/74] Update switchbotBot --- switchbotBot | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotBot b/switchbotBot index 9590c24..3df8363 100644 --- a/switchbotBot +++ b/switchbotBot @@ -98,6 +98,3 @@ def push(buttonNumber = 0) runIn(2, refresh) } } - -def readDeviceDetails(details) { -} From 9451214c62faaef929b9db31b4fab832c9c31c57 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:32:13 -0400 Subject: [PATCH 58/74] Update switchbotContactSensor --- switchbotContactSensor | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotContactSensor b/switchbotContactSensor index b649989..36df0f9 100644 --- a/switchbotContactSensor +++ b/switchbotContactSensor @@ -70,6 +70,3 @@ def parse(body) sendEvent(name: "motion", value: ([true, "DETECTED"].contains(body.moveDetected)) ? "active" : "inactive") } } - -def readDeviceDetails(details) { -} From ef18a1d05d7882ff3172e426f11c4dee7b6546b4 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:32:21 -0400 Subject: [PATCH 59/74] Update switchbotCurtain --- switchbotCurtain | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotCurtain b/switchbotCurtain index 8d53db3..a36d13a 100644 --- a/switchbotCurtain +++ b/switchbotCurtain @@ -203,6 +203,3 @@ def correctedPositions(rawPos) return retVal } - -def readDeviceDetails(details) { -} From 59c30ce612032e4a7555556c3e18dd416650f734 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:32:30 -0400 Subject: [PATCH 60/74] Update switchbotHumidifier --- switchbotHumidifier | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotHumidifier b/switchbotHumidifier index 4608179..cc60667 100644 --- a/switchbotHumidifier +++ b/switchbotHumidifier @@ -170,6 +170,3 @@ def setMode(mode) runIn(2, refresh) } } - -def readDeviceDetails(details) { -} From 5aa32be9ff27bcf18edacab2d48306538ba760cf Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:32:40 -0400 Subject: [PATCH 61/74] Update switchbotIRDevice --- switchbotIRDevice | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotIRDevice b/switchbotIRDevice index 82dd52e..707a4bf 100644 --- a/switchbotIRDevice +++ b/switchbotIRDevice @@ -71,9 +71,6 @@ def sendCommand(command, parameter = "default") } } -def readDeviceDetails(details) { -} - import groovy.transform.Field @Field standardCommands = From 8914c4c8e5c3a13e55ed2cf16e15c768c819c2b8 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:32:46 -0400 Subject: [PATCH 62/74] Update switchbotMeter --- switchbotMeter | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotMeter b/switchbotMeter index 7aafa5c..5e8bea0 100644 --- a/switchbotMeter +++ b/switchbotMeter @@ -78,6 +78,3 @@ def parse(body) sendEvent(name: "battery", value: battery) } } - -def readDeviceDetails(details) { -} From a95d3eb32653045904f524810d7b5aa0f9c48a31 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:32:53 -0400 Subject: [PATCH 63/74] Update switchbotMotionSensor --- switchbotMotionSensor | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotMotionSensor b/switchbotMotionSensor index 0dfd71d..186281a 100644 --- a/switchbotMotionSensor +++ b/switchbotMotionSensor @@ -61,6 +61,3 @@ def parse(body) sendEvent(name: "motion", value: ([true, "DETECTED"].contains(body.moveDetected)) ? "active" : "inactive") } } - -def readDeviceDetails(details) { -} From 0c4ef6efda7e5353c7412fdd14e6c3670c6f9981 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:32:59 -0400 Subject: [PATCH 64/74] Update switchbotPlugMini --- switchbotPlugMini | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotPlugMini b/switchbotPlugMini index 005769a..6bff3e1 100644 --- a/switchbotPlugMini +++ b/switchbotPlugMini @@ -100,6 +100,3 @@ def toggle() { writeDeviceCommand("toggle") } - -def readDeviceDetails(details) { -} From 73bb7bfd4eaa185aaff6fec820ce533a3cf9aa2a Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:33:07 -0400 Subject: [PATCH 65/74] Update switchbotSmartLock --- switchbotSmartLock | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotSmartLock b/switchbotSmartLock index 0f54fe6..71802ac 100644 --- a/switchbotSmartLock +++ b/switchbotSmartLock @@ -89,6 +89,3 @@ def unlock() { writeDeviceCommand("unlock") } - -def readDeviceDetails(details) { -} From e950cb0b42176b3f53833af6e7d704afc2afc2f8 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:33:14 -0400 Subject: [PATCH 66/74] Update switchbotStripLight --- switchbotStripLight | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbotStripLight b/switchbotStripLight index 11514bc..9a0a97a 100644 --- a/switchbotStripLight +++ b/switchbotStripLight @@ -266,6 +266,3 @@ def tempToColor(temp) return genericName } - -def readDeviceDetails(details) { -} From 1818bc412a1da6bcb2cf83c1269fb270a070f11d Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:41:40 -0400 Subject: [PATCH 67/74] Update switchbotKeypadTouch --- switchbotKeypadTouch | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 7a1d402..0ba25df 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -26,6 +26,10 @@ import groovy.json.JsonBuilder import groovy.transform.Field import java.util.Calendar +import javax.crypto.* +import javax.crypto.spec.* + +import hubitat.helper.HexUtils @Field static final String PERMANENT = "permanent" @Field static final String TIMED = "timeLimit" @@ -89,7 +93,7 @@ def updated() { def refresh() { - parent?.rereadDevices(device.getDeviceNetworkId()) + readDeviceDetails() } def parse(body) @@ -204,7 +208,16 @@ def deleteKeyByName(String name, boolean recordPendingCommand=true) { } } -def readDeviceDetails(details) { +def readDeviceDetails() +{ + if(!parent) { return } + + parent.readDevices() + devices = parent.getDevices()?.deviceList + details = devices?.find { it.deviceId == parent.getId(device.getDeviceNetworkId()) } + + logDebug("Details=" + details) + atomicState.lockCodes = [] atomicState.lockCodesByName = [:] def expiredCodes = [] @@ -268,9 +281,30 @@ def getCodes() { def codes = [:] int i = 0 for (it in atomicState.lockCodes) { - codes[i++] = ["name": it.name, "code": parent?.decryptPasscode(it.iv, it.code)] + codes[i++] = ["name": it.name, "code": decryptPasscode(it.iv, it.code)] } String str = new JsonBuilder(codes).toString() return encryptionEnabled ? encrypt(str) : str } + +def decryptPasscode(String iv, String encrypted) { + if (!parent) return + parent.cacheSecretKey() + String secretKey = parent.getDataValue("developerSecretKey") + try { + byte[] ivBytes = HexUtils.hexStringToByteArray(iv) + def ivSpec = new IvParameterSpec(ivBytes) + def skeySpec = new SecretKeySpec(HexUtils.hexStringToByteArray(secretKey), "AES") + + def cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec) + byte[] original = cipher.doFinal(encrypted.decodeBase64()) + + return new String(original) + } catch (Exception ex) { + logDebug(ex) + } + + return null +} From 13282f732d927b0647062a47ed9f98cb75d89a58 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:41:50 -0400 Subject: [PATCH 68/74] Update switchbotSystem --- switchbotSystem | 54 ++++++++----------------------------------------- 1 file changed, 8 insertions(+), 46 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index b1ca2be..0a71310 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -53,6 +53,7 @@ metadata command "cacheHttpParams" command "cacheToken" + command "cacheSecretKey" command "parse", ["eventMap"] @@ -101,20 +102,6 @@ def initialize() } } -def rereadDevices(String requestorDNI=null) -{ - try - { - readDevices() - createChildDevices(false, requestorDNI) - } - catch (Exception e) - { - logDebug("rereadDevices() failed: ${e.message}") - return - } -} - def updated() { configure() @@ -176,6 +163,12 @@ def cacheToken() updateDataValue("token", openToken) } +def cacheSecretKey() +{ + // workaround to support app that isn't this driver's parent + updateDataValue("developerSecretKey", secretKey) +} + def cacheHttpParams() { // workaround to support app that isn't this driver's parent @@ -212,7 +205,7 @@ def refreshFromParent(child) } } -def createChildDevices(boolean createNewDevices=true, String requestorDNI=null) +def createChildDevices(boolean createNewDevices=true) { def devices = getDevices() @@ -256,9 +249,6 @@ def createChildDevices(boolean createNewDevices=true, String requestorDNI=null) childDevice = createChildDevice(it.deviceName, it.deviceId, it.deviceType) } - - if (childDevice && (requestorDNI == null || requestorDNI == childDevice.getDeviceNetworkId())) - childDevice.readDeviceDetails(it) } } @@ -533,34 +523,6 @@ def hmac_sha256(String secretKey, String data) return org.apache.commons.codec.binary.Base64.encodeBase64String(sign) } -def decryptPasscode(String iv, String encrypted) { - try { - byte[] ivBytes = hexStringToByteArray(iv) - def ivSpec = new IvParameterSpec(ivBytes) - def skeySpec = new SecretKeySpec(hexStringToByteArray(secretKey), "AES") - - def cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec) - byte[] original = cipher.doFinal(encrypted.decodeBase64()) - - return new String(original) - } catch (Exception ex) { - logDebug(ex) - } - - return null -} - -byte[] hexStringToByteArray(String s) { - int len = s.length() - byte[] data = new byte[len / 2] - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i+1), 16)) - } - return data -} - def httpGetExec(params, throwToCaller = false) { logDebug("httpGetExec(${params})") From 58d3826632797d38c4f7c1054ca2d413e971be7a Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 19:44:29 -0400 Subject: [PATCH 69/74] Update switchbotSystem --- switchbotSystem | 2 -- 1 file changed, 2 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index 0a71310..850b10c 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -36,8 +36,6 @@ Change history: */ -import javax.crypto.* -import javax.crypto.spec.* import org.apache.commons.codec.binary.Base64 metadata From 856ca0e359a2ca15f4742ea4f23e090bdafaa7bb Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 21:41:09 -0400 Subject: [PATCH 70/74] Update switchbotSystem --- switchbotSystem | 249 ++++++++++++------------------------------------ 1 file changed, 62 insertions(+), 187 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index 850b10c..956b267 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -18,7 +18,7 @@ limitations under the License. Change history: -0.9.24 - dsegall - added Keypad Touch +0.x.x (05/21 PM) - Bugfixes and new device aliases 0.9.21 - tomw - Added temperature, humidity, and lightLevel support for Hub 2 (in SwitchBot Meter driver). Added Indoor/Outdoor Thermo-Hygrometer (in SwitchBot Meter driver). @@ -36,8 +36,6 @@ Change history: */ -import org.apache.commons.codec.binary.Base64 - metadata { definition(name: "SwitchBot System", namespace: "tomw", author: "tomw", importUrl: "") @@ -107,8 +105,7 @@ def updated() def configure() { - state.clear - + state.clear() initialize() } @@ -155,23 +152,11 @@ def checkCommStatus() } } -def cacheToken() -{ - // workaround to support app that isn't this driver's parent - updateDataValue("token", openToken) -} +def cacheToken() { updateDataValue("token", openToken) } -def cacheSecretKey() -{ - // workaround to support app that isn't this driver's parent - updateDataValue("developerSecretKey", secretKey) -} +def cacheSecretKey() { updateDataValue("secretKey", secretKey) } -def cacheHttpParams() -{ - // workaround to support app that isn't this driver's parent - updateDataValue("params", groovy.json.JsonOutput.toJson(genParamsMain(""))) -} +def cacheHttpParams() { updateDataValue("params", groovy.json.JsonOutput.toJson(genParamsMain(""))) } def parse(Map eventMap) { @@ -203,7 +188,7 @@ def refreshFromParent(child) } } -def createChildDevices(boolean createNewDevices=true) +def createChildDevices() { def devices = getDevices() @@ -229,24 +214,22 @@ def createChildDevices(boolean createNewDevices=true) } if(["Plug Mini (US)", "Plug Mini (JP)"].contains(it.deviceType)) { it.deviceType = "Plug Mini" } - if(it.deviceType == "Color Bulb") { it.deviceType = "Strip Light" } - if(it.deviceType == "Curtain3") { it.deviceType = "Curtain" } - - def childDevice = findChildDevice(it.deviceId, it.deviceType) - if(it.deviceId && it.deviceName && it.deviceType) + else if(it.deviceType == "Color Bulb") { it.deviceType = "Strip Light" } + else if(it.deviceType == "Curtain3") { it.deviceType = "Curtain" } + else if(it.deviceType == "Smart Lock Pro") { it.deviceType = "Smart Lock" } + + if((it.deviceId && it.deviceName && it.deviceType) && !findChildDevice(it.deviceId, it.deviceType)) { - if (!childDevice && createNewDevices) { - if(["Curtain", "Blind Tilt"].contains(it.deviceType)) + if(["Curtain", "Blind Tilt"].contains(it.deviceType)) + { + // only create children for ungrouped Curtains and group masters + if(!((it.group != true) || (it.master == true))) { - // only create children for ungrouped Curtains and group masters - if(!((it.group != true) || (it.master == true))) - { - continue - } + continue } - - childDevice = createChildDevice(it.deviceName, it.deviceId, it.deviceType) } + + createChildDevice(it.deviceName, it.deviceId, it.deviceType) } } @@ -307,7 +290,7 @@ def createChildDevice(name, id, deviceType) { try { - def customDevTypes = ["Bot", "Curtain", "Meter", "IR Device", "Humidifier", "Strip Light", "Smart Lock", "Motion Sensor", "Contact Sensor", "Plug Mini", "Blind Tilt", "Keypad Touch", "Smart Lock Pro"] + def customDevTypes = ["Bot", "Curtain", "Meter", "IR Device", "Humidifier", "Strip Light", "Smart Lock", "Motion Sensor", "Contact Sensor", "Plug Mini", "Blind Tilt", "Smart Lock Pro", "Keypad Touch"] def genericDevTypes = [] deviceType = deviceType.toString() @@ -323,8 +306,6 @@ def createChildDevice(name, id, deviceType) logDebug("createChildDevice: deviceType not supported") throw new Exception("deviceType not supported") } - - if (deviceType == "Smart Lock Pro") deviceType = "Smart Lock" def virtDevType = ((customDevTypes.contains(deviceType)) ? "SwitchBot " : "Generic Component ") + deviceType def virtDevData = [name: childName(name, deviceType), label: childName(name, deviceType), isComponent: false] @@ -350,113 +331,54 @@ def createChildDevice(name, id, deviceType) } } -def readDevices() +def readEndpoint(ep) { try { - def resp = httpGetExec(genParamsMain("devices"), true) - - if(resp) - { - setDevices(resp?.data?.body) - } + def resp = httpExec("GET", genParamsMain(ep), true) + def val = resp?.data?.body + if(val) { setState(ep, val) } sendEvent(name: "commStatus", value: "good") - } - catch (Exception e) - { - logDebug("readDevices() failed: ${e.message}") - sendEvent(name: "commStatus", value: "error") - throw(e) - } - -} - -def setDevices(devices) -{ - state.devices = devices -} - -def getDevices() -{ - return state.devices -} - -def readScenes() -{ - try - { - def resp = httpGetExec(genParamsMain("scenes"), true) - - if(resp) - { - setScenes(resp?.data?.body) - } - - sendEvent(name: "commStatus", value: "good") + return val } catch (Exception e) { - logDebug("readScenes() failed: ${e.message}") + logDebug("readEndpoint(${ep}) failed: ${e.message}") sendEvent(name: "commStatus", value: "error") throw(e) - } - + } } -def setScenes(scenes) -{ - state.scenes = scenes -} +void setState(name, value) { state[name] = value } +def getState(name) { return state[name] } -def getScenes() -{ - return state.scenes -} +def readDevices() { return readEndpoint("devices") } +void setDevices(devices) { setState("devices", devices) } +def getDevices() { return getState("devices") } + +def readScenes() { return readEndpoint("scenes") } +void setScenes(scenes) { setState("scenes", scenes) } +def getScenes() { return getState("scenes") } def executeScene(sceneId) { - try - { - return httpExec("POST", genParamsMain("scenes/${sceneId}/execute"), true)?.data - } - catch (Exception e) - { - logDebug("executeScene failed: ${e.message}") - throw(e) - } + return httpExec("POST", genParamsMain("scenes/${sceneId}/execute"))?.data } def writeDeviceCommand(id, command, parameter, commandType) { - try - { - def body = [command: command, parameter: parameter, commandType: commandType] - body = groovy.json.JsonOutput.toJson(body) - - return httpExec("POST", genParamsMain("devices/${id}/commands", body), true)?.data - } - catch (Exception e) - { - logDebug("writeDeviceCommand failed: ${e.message}") - throw(e) - } + def body = [command: command, parameter: parameter, commandType: commandType] + body = groovy.json.JsonOutput.toJson(body) + + return httpExec("POST", genParamsMain("devices/${id}/commands", body))?.data } def readDeviceStatus(id) { - try - { - def resp = httpExec("GET", genParamsMain("devices/${id}/status"), true)?.data - return resp - } - catch (Exception e) - { - logDebug("readDeviceStatus failed: ${e.message}") - throw(e) - } + return httpExec("GET", genParamsMain("devices/${id}/status"))?.data } def getBaseURI() @@ -521,85 +443,38 @@ def hmac_sha256(String secretKey, String data) return org.apache.commons.codec.binary.Base64.encodeBase64String(sign) } -def httpGetExec(params, throwToCaller = false) +def httpExec(String operation, Map params, Boolean throwToCaller = false) { - logDebug("httpGetExec(${params})") + def result = null - try - { - def result - httpGet(params) - { resp -> - if (resp) - { - logDebug("resp.data = ${resp.data}") - result = resp - } - } - return result - } - catch (Exception e) - { - logDebug("httpGetExec() failed: ${e.message}") - //logDebug("status = ${e.getResponse().getStatus().toInteger()}") - if(throwToCaller) - { - throw(e) - } - } -} - -def httpPostExec(params, throwToCaller = false) -{ - logDebug("httpPostExec(${params})") + logDebug("httpExec(${operation}, ${params})") - try - { - def result - httpPost(params) - { resp -> - if (resp) - { - logDebug("resp.data = ${resp.data}") - result = resp - } - } - return result - } - catch (Exception e) - { - logDebug("httpPostExec() failed: ${e.message}") - //logDebug("status = ${e.getResponse().getStatus().toInteger()}") - if(throwToCaller) - { - throw(e) - } + def httpClosure = + { resp -> + result = resp + //logDebug("result.data = ${result.data}") } -} - -def httpExec(operation, params, throwToCaller = false) -{ - def res + + def httpOp switch(operation) { - default: - logDebug("unsupported Http operation") - - if(throwToCaller) - { - throw new Exception("unsupported Http operation") - } - break - case "POST": - res = httpPostExec(params, throwToCaller) + httpOp = this.delegate.&httpPost break - case "GET": - res = httpGetExec(params, throwToCaller) + httpOp = this.delegate.&httpGet break } - return res + try + { + httpOp(params, httpClosure) + return result + } + catch (Exception e) + { + logDebug("httpExec(${operation}, ${params}) failed: ${e}") + if(throwToCaller) { throw(e) } + } } From a0df0f9cb1c2ba37c40fd98e61e7548628b9d8c3 Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 21:43:51 -0400 Subject: [PATCH 71/74] Update switchbotKeypadTouch --- switchbotKeypadTouch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 0ba25df..76429b0 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -291,7 +291,7 @@ def getCodes() { def decryptPasscode(String iv, String encrypted) { if (!parent) return parent.cacheSecretKey() - String secretKey = parent.getDataValue("developerSecretKey") + String secretKey = parent.getDataValue("secretKey") try { byte[] ivBytes = HexUtils.hexStringToByteArray(iv) def ivSpec = new IvParameterSpec(ivBytes) From d4d0e55855a17894956d6552b43955f8de6c393b Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Sun, 12 May 2024 21:47:38 -0400 Subject: [PATCH 72/74] Update switchbotKeypadTouch --- switchbotKeypadTouch | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 76429b0..5dafc87 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -212,8 +212,7 @@ def readDeviceDetails() { if(!parent) { return } - parent.readDevices() - devices = parent.getDevices()?.deviceList + devices = parent.readDevices()?.deviceList details = devices?.find { it.deviceId == parent.getId(device.getDeviceNetworkId()) } logDebug("Details=" + details) From d5221046ec046c5188f85deaef93b1f3975641ad Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Fri, 17 May 2024 12:06:07 -0400 Subject: [PATCH 73/74] got rid of pending commands count the event callbacks are unreliable --- switchbotKeypadTouch | 57 ++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/switchbotKeypadTouch b/switchbotKeypadTouch index 5dafc87..6c6b969 100644 --- a/switchbotKeypadTouch +++ b/switchbotKeypadTouch @@ -55,7 +55,6 @@ metadata capability "Initialize" command "createKey", [[name: "Name*", type: "STRING"], [name: "Password*", type: "STRING"], [name: "Type", type: "ENUM", constraints: [PERMANENT, TIMED, DISPOSABLE, EMERGENCY]], [name: "Start Time", type: "STRING"], [name: "End Time", type: "STRING"]] - command "deleteKey", [[name: "ID*", type: "STRING"]] command "deleteKeyByName", [[name: "Name*", type: "STRING"]] } @@ -80,8 +79,6 @@ def logDebug(msg) def initialize() { - atomicState.pendingCommands = 0 - atomicState.needsReread = false sendEvent(name: "codeLength", value: 12) sendEvent(name: "maxCodes", value: 100) refresh() @@ -101,20 +98,15 @@ def parse(body) if (logEnable) logDebug("parse: ${body}") - if (atomicState.pendingCommands > 0) atomicState.pendingCommands-- - if (body.result == "success") { - atomicState.needsReread = true if (body.eventName == "createKey") sendEvent(name: "codeChanged", value: "added") else if (body.eventName == "deleteKey") sendEvent(name: "codeChanged", value: "deleted") - if (atomicState.pendingCommands == 0) { - atomicState.needsReread = false - logDebug("Refreshing...") - runIn(1, "refresh") - } + atomicState.lastSuccessfulModificationTime = new Date() + logDebug("Scheduling refresh task...") + runIn(30000, "refresh") } else sendEvent(name: "codeChanged", value: "failed") } @@ -165,13 +157,11 @@ def createKey(String name, String code, String type=PERMANENT, String startTimeS } Map params = ["name": name, "type": type, "password": code, "startTime": startTime, "endTime": endTime] - atomicState.pendingCommands++ try { writeDeviceCommand("createKey", params) } catch (Exception e) { logDebug("createKey failed: ${e.getMessage()}") - atomicState.pendingCommands-- } } } @@ -186,25 +176,36 @@ Calendar timeToday(String time) { return today } -def deleteKey(String id, boolean recordPendingCommand=true) { - if (recordPendingCommand) - atomicState.pendingCommands++ - +def deleteKey(String id) { logDebug("deleteKey id=${id}") try { writeDeviceCommand("deleteKey", ["id": id]) } catch (Exception e) { logDebug("deleteKey failed: ${e.getMessage()}") - atomicState.pendingCommands-- } } -def deleteKeyByName(String name, boolean recordPendingCommand=true) { - String id = atomicState.lockCodesByName[name] - logDebug("deleteKeyByName found key ${id}") - if (id) { - deleteKey(id, recordPendingCommand) +def deleteKeyByName(String name) { + Map lockCode = atomicState.lockCodesByName[name] + logDebug("deleteKeyByName found key ${lockCode}") + if (lockCode) { + deleteAndRemoveLockCode(lockCode) + } +} + +def deleteAndRemoveLockCode(Map lockCode) { + if (lockCode) { + def newCodes = [] + def newCodesByName = [:] + + newCodes.addAll(atomicState.lockCodes) + newCodesByName.putAll(atomicState.lockCodesByName) + newCodes.remove(lockCode) + newCodesByName.remove(lockCode.name) + atomicState.lockCodes = newCodes + atomicState.lockCodesByName = newCodesByName + deleteKey(lockCode.id.toString()) } } @@ -227,8 +228,9 @@ def readDeviceDetails() logDebug("Details=" + details) for (it in details?.keyList) { logDebug("Code=" + it) - newCodes << ["id": it.id, "name": it.name, "code": it.password, "iv": it.iv, "status": it.status] - newCodesByName[it.name] = it.id + Map lockCode = ["id": it.id, "name": it.name, "code": it.password, "iv": it.iv, "status": it.status] + newCodes << lockCode + newCodesByName[it.name] = lockCode if (it.status == PASSCODE_EXPIRED) expiredCodes << it.id } @@ -252,18 +254,17 @@ def cleanExpiredCodes(expiredCodes) { boolean clean = deleteExpiredCodes == null ? true : deleteExpiredCodes logDebug("cleanExpiredCodes=${clean} size=${expiredCodes.size}") if (clean) { - atomicState.pendingCommands += expiredCodes.size() for (code in expiredCodes) { String codeStr = code.toString() logDebug("Deleting expired code ${codeStr}") - deleteKey(codeStr, false) + deleteKey(codeStr) } } } def deleteCode(position) { if (position >= 0 && position < atomicState.lockCodes.size() && atomicState.lockCodes[position]) - deleteKey(atomicState.lockCodes[position].id.toString()) + deleteAndRemoveLockCode(atomicState.lockCodes[position]) } def setCodeLength() { From 39827bb1fa1bab3dfe3fa76a6879d12f9f6c828e Mon Sep 17 00:00:00 2001 From: dds82 <47287470+dds82@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:13:06 -0400 Subject: [PATCH 74/74] Update switchbotSystem support for lock ultra --- switchbotSystem | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/switchbotSystem b/switchbotSystem index 956b267..605c5a7 100644 --- a/switchbotSystem +++ b/switchbotSystem @@ -216,7 +216,7 @@ def createChildDevices() if(["Plug Mini (US)", "Plug Mini (JP)"].contains(it.deviceType)) { it.deviceType = "Plug Mini" } else if(it.deviceType == "Color Bulb") { it.deviceType = "Strip Light" } else if(it.deviceType == "Curtain3") { it.deviceType = "Curtain" } - else if(it.deviceType == "Smart Lock Pro") { it.deviceType = "Smart Lock" } + else if(it.deviceType.contains("Smart Lock")) { it.deviceType = "Smart Lock" } if((it.deviceId && it.deviceName && it.deviceType) && !findChildDevice(it.deviceId, it.deviceType)) { @@ -290,7 +290,7 @@ def createChildDevice(name, id, deviceType) { try { - def customDevTypes = ["Bot", "Curtain", "Meter", "IR Device", "Humidifier", "Strip Light", "Smart Lock", "Motion Sensor", "Contact Sensor", "Plug Mini", "Blind Tilt", "Smart Lock Pro", "Keypad Touch"] + def customDevTypes = ["Bot", "Curtain", "Meter", "IR Device", "Humidifier", "Strip Light", "Smart Lock", "Motion Sensor", "Contact Sensor", "Plug Mini", "Blind Tilt", "Smart Lock Pro", "Smart Lock Ultra", "Keypad Touch"] def genericDevTypes = [] deviceType = deviceType.toString()