/**
* Tuya Scene Switch TS004F w/ healthStatus driver for Hubitat Elevation hub.
*
* Supports Tuya (Moes, Zemismart, LoraTap, .....) buttons and scene switches and remotes (1,2,3,4,5,6 buttons), smart knobs, Konke, icasa
*
* https://community.hubitat.com/t/release-tuya-scene-switch-ts004f-driver-w-healthstatus/92823
*
* 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.
*
* The inital version was based on ST DH "Zemismart Button", namespace: SangBoy, author: YooSangBeom
*
* ver. 1.0.0 2021-05-08 kkossev - SmartThings version
* ver. 2.0.0 2021-10-03 kkossev - First version for Hubitat in 'Scene Control'mode - AFTER PAIRING FIRST to Tuya Zigbee gateway!
* ver. 2.1.0 2021-10-20 kkossev - typos fixed; button wrong event names bug fixed; extended debug logging; added experimental switchToDimmerMode command
* ver. 2.1.1 2021-10-20 kkossev - numberOfButtons event bug fix;
* ver. 2.2.0 2021-10-20 kkossev - First succesfuly working version with HE!
* ver. 2.2.1 2021-10-23 kkossev - added "Reverse button order" preference option
* ver. 2.2.2 2021-11-17 kkossev - added battery reporting capability; added buttons handlers for use in Hubutat Dashboards; code cleanup
* ver. 2.2.3 2021-12-01 kkossev - added fingerprint for Tuya Remote _TZ3000_pcqjmcud
* ver. 2.2.4 2021-12-05 kkossev - added support for 'YSR-MINI-Z Remote TS004F'
* ver. 2.3.0 2022-02-13 kkossev - added support for 'Tuya Smart Knob TS004F'
* ver. 2.4.0 2022-03-31 kkossev - added support for 'MOES remote TS0044', singleThreaded: true; bug fix: debouncing timer was not started for TS0044
* ver. 2.4.1 2022-04-23 kkossev - improved tracing of debouncing logic code; option [overwrite: true] is set explicitely on debouncing timer restart; debounce timer increased to 1000ms
* ver. 2.4.2 2022-05-07 kkossev - added LoraTap 6 button Scene Controller; device.getDataValue bug fix;
* ver. 2.4.3 2022-09-18 kkossev - added TS0042 Tuya Zigbee 2 Gang Wireless Smart Switch; removed 'release' event for TS0044 switches (not supported by hardware); 'release' digital event bug fix.
* ver. 2.4.4 2022-10-22 kkossev - _TZ3000_vp6clf9d fingerprint correction; importURL changed to dev. branch; added _TZ3000_w8jwkczz and other TS0041, TS0042, TS0043, TS004 fingerprints
* ver. 2.4.5 2022-10-27 kkossev - added icasa ICZB-KPD18S 8 button controller.
* ver. 2.4.6 2022-11-20 kkossev - added TS004F _TZ3000_ja5osu5g - 1 button!; isTuya() bug fix
* ver. 2.4.7 2022-12-22 kkossev - added TS004F _TZ3000_rco1yzb1 LIDL Smart Button SSBM A1; added _TZ3000_u3nv1jwk
* ver. 2.5.0 2023-01-14 kkossev - bug fix: battery percentage remaining automatic reporting was not configured, now hardcoded to 8 hours; bug fix: 'released'event; debug info improvements; declared supportedButtonValues attribute
* ver. 2.5.1 2023-01-20 kkossev - battery percentage remaining HomeKit compatibility
* ver. 2.5.2 2023-01-28 kkossev - _TZ3000_vp6clf9d (TS0044) debouncing; added Loratap TS0046 (6 buttons);
* ver. 2.6.0 2023-01-28 kkossev - added healthStatus; Initialize button is disabled;
* ver. 2.6.1 2023-02-05 kkossev - added _TZ3000_mh9px7cq; isSmartKnob() typo fix; added capability 'Health Check'; added powerSource attribute 'battery'; added dummy ping() code; added _TZ3000_famkxci2
* ver. 2.6.2 2023-02-23 kkossev - added Konke button model: 3AFE280100510001 ; LoraTap _TZ3000_iszegwpd TS0046 buttons 5&6;
* ver. 2.6.3 2023-03-11 kkossev - added TS0215 _TYZB01_qm6djpta _TZ3000_fsiepnrh _TZ3000_p6ju8myv; added state.stats{rxCtr,txCtr,rejoinCtr}; added Advanced options; added batteryReportingOptions; battery reporting is not changed by default!
* ver. 2.6.4 2023-04-27 kkossev - added Sonoff SNZB-01; added IKEA Tradfri Shortcut Button E1812; added AC0251600NJ/AC0251100NJ OSRAM Lightify Switch Mini; added TS0041 _TZ3000_fa9mlvja 1 button; TS0215A _TZ3000_2izubafb inClusters correction
* ver. 2.6.5 2023-05-15 kkossev - TS0215A _TZ3000_pkfazisv iAlarm (Meian) SOS button fingerprint correction; number of buttons and supportedValues correction for SOS buttons; added _TZ3000_abrsvsou
* ver. 2.6.6 2023-05-30 kkossev - reverseButton default value bug fix;
*
* ver. 2.6.9 2023-10-14 kkossev - REVERTED BACK TO VERSION 2.6.6 timeStamp 2023/05/30 1:51 PM
* ver. 2.6.10 2023-12-01 kkossev - (dev. branch) added _TZ3000_ur5fpg7p in the needsDebouncing list; added Sonoff SNZB-01P
*
* - TODO: debounce timer configuration (1000ms may be too low when repeaters are in use);
* - TODO: batteryReporting is not initialized!
* - TODO: unschedule jobs from other drivers: https://community.hubitat.com/t/moes-4-button-zigbee-switch/78119/20?u=kkossev
* - TODO: configre (override) the numberOfButtons in the AdvancedOptions
* - TODO: Lightify initialization like in the stock HE driver'; add Aqara button;
* - TODO: Sonoff button - battery reporting to be enabled by default; Refresh to read battery level/voltage';
* - TODO: add IAS Zone (0x0500) and IAS ACE (0x0501) support; enroll for TS0215/TS0215A
* - TODO: Debug logs off after 24 hours
* - TODO: simulate double-click for the 4-button knobs
* - TODO: Remove battery percentage reporting configuration for TS0041 and TS0046 : https://github.com/Koenkk/zigbee2mqtt/issues/6313#issuecomment-780746430 // https://github.com/Koenkk/zigbee2mqtt/issues/15340
* - TODO: Try to send default responses after button press for TS004F devices : https://github.com/Koenkk/zigbee2mqtt/issues/8149
* - TODO: Advanced option 'batteryVoltage' 'enum' ['report voltage', 'voltage + battery%'']
* - TODO: calculate battery % from Voltage event for Konke button!
* - TODO: add 'auto revert to scene mode' option
* - TODO: add supports forZigbee identify cluster (0x0003) ( activate LEDs as feedback that HSM is armed/disarmed ..)
*
*/
def version() { "2.6.10" }
def timeStamp() {"2023/12/01 9:58 AM"}
@Field static final Boolean DEBUG = false
@Field static final Integer healthStatusCountTreshold = 4
import groovy.transform.Field
import hubitat.helper.HexUtils
import hubitat.device.HubMultiAction
import groovy.json.JsonOutput
metadata {
definition (name: "Tuya Scene Switch TS004F", namespace: "kkossev", author: "Krassimir Kossev", importUrl: "https://raw.githubusercontent.com/kkossev/Hubitat/development/Drivers/Tuya%20TS004F/TS004F.groovy", singleThreaded: true ) {
capability "Refresh"
capability "PushableButton"
capability "DoubleTapableButton"
capability "HoldableButton"
capability "ReleasableButton"
capability "Battery"
capability "PowerSource"
capability "Configuration"
capability "Health Check"
attribute "supportedButtonValues", "JSON_OBJECT"
attribute "switchMode", "enum", ["dimmer", "scene"]
attribute "batteryVoltage", "number"
attribute "healthStatus", "enum", ["offline", "online"]
attribute "powerSource", "enum", ["battery", "dc", "mains", "unknown"]
if (DEBUG == true) {
command "switchMode", [[name: "mode*", type: "ENUM", constraints: ["dimmer", "scene"], description: "Select device mode"]]
command "test", [[name: "test", type: "STRING", description: "test", defaultValue : ""]]
}
fingerprint inClusters: "0000,0001,0003,0004,0006,1000", outClusters: "0019,000A,0003,0004,0005,0006,0008,1000", manufacturer: "_TZ3000_xabckq1v", model: "TS004F", deviceJoinName: "Tuya Scene Switch TS004F"
fingerprint inClusters: "0000,0001,0003,0004,0006,1000", outClusters: "0019,000A,0003,0004,0005,0006,0008,1000", manufacturer: "_TZ3000_pcqjmcud", model: "TS004F", deviceJoinName: "YSR-MINI-Z Remote TS004F"
fingerprint inClusters: "0000,0001,0003,0004,0006,1000", outClusters: "0019,000A,0003,0004,0005,0006,0008,1000", manufacturer: "_TZ3000_4fjiwweb", model: "TS004F", deviceJoinName: "Tuya Smart Knob TS004F"
fingerprint inClusters: "0000,0001,0003,0004,0006,1000", outClusters: "0019,000A,0003,0004,0005,0006,0008,1000", manufacturer: "_TZ3000_uri7ongn", model: "TS004F", deviceJoinName: "Tuya Smart Knob TS004F" // not tested
fingerprint inClusters: "0000,0001,0003,0004,0006,1000", outClusters: "0019,000A,0003,0004,0005,0006,0008,1000", manufacturer: "_TZ3000_ixla93vd", model: "TS004F", deviceJoinName: "Tuya Smart Knob TS004F" // not tested
fingerprint inClusters: "0000,0001,0003,0004,0006,1000", outClusters: "0019,000A,0003,0004,0005,0006,0008,1000", manufacturer: "_TZ3000_qja6nq5z", model: "TS004F", deviceJoinName: "Tuya Smart Knob TS004F" // not tested
fingerprint inClusters: "0000,0001,0003,0004,0006,1000", outClusters: "0019,000A,0003,0004,0005,0006,0008,1000", manufacturer: "_TZ3000_csflgqj2", model: "TS004F", deviceJoinName: "Tuya Smart Knob TS004F" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0003,0004,0006,1000,0000", outClusters:"0003,0004,0005,0006,0008,1000,0019,000A", model:"TS004F", manufacturer:"_TZ3000_abrsvsou", deviceJoinName: "Tuya Smart Knob TS004F" //KK
fingerprint inClusters: "0000,0001,0006", outClusters: "0019,000A", manufacturer: "_TZ3400_keyjqthh", model: "TS0041", deviceJoinName: "Tuya YSB22 TS0041"
fingerprint inClusters: "0000,0001,0006", outClusters: "0019,000A", manufacturer: "_TZ3400_tk3s5tyg", model: "TS0041", deviceJoinName: "Tuya TS0041" // not tested
fingerprint inClusters: "0000,0001,0006", outClusters: "0019,000A", manufacturer: "_TZ3000_xrqsdxq6", model: "TS0041", deviceJoinName: "Zigbee Tuya 1 Button" // not tested
fingerprint inClusters: "0000,0001,0006", outClusters: "0019,000A", manufacturer: "_TZ3400_tk3s5tyg", model: "TS0041", deviceJoinName: "Zigbee Tuya 1 Button"
fingerprint inClusters: "0000,0001,0006", outClusters: "0019,000A", manufacturer: "_TZ3000_tk3s5tyg", model: "TS0041", deviceJoinName: "Zigbee Tuya 1 Button"
fingerprint inClusters: "0000,0001,0006", outClusters: "0019,000A", manufacturer: "_TZ3000_adkvzooy", model: "TS0041", deviceJoinName: "Zigbee Tuya 1 Button" // not tested
fingerprint inClusters: "0000,000A,0001,0006", outClusters: "0019,000A", manufacturer: "_TZ3000_peszejy7", model: "TS0041", deviceJoinName: "Zigbee Tuya 1 Button"
fingerprint inClusters: "0000,0001,0006", outClusters: "0019", manufacturer: "_TYZB02_key8kk7r", model: "TS0041", deviceJoinName: "Zigbee Tuya 1 Button"
fingerprint profileId: "0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0041", manufacturer:"_TZ3000_fa9mlvja" // https://www.aliexpress.com/item/1005005363529624.html
fingerprint inClusters: "0000,0001,0003,0004,0006,1000,E001", outClusters: "0019,000A,0003,0004,0006,0008,1000", manufacturer: "_TZ3000_ja5osu5g", model: "TS004F", deviceJoinName: "MOES Smart Button (ZT-SY-SR-MS)" // MOES ZigBee IP55 Waterproof Smart Button Scene Switch & Wireless Remote Dimmer (ZT-SY-SR-MS)
fingerprint inClusters: "0000,0001,0003,0004,0006,1000,E001", outClusters: "0019,000A,0003,0004,0005,0006,0008,1000", manufacturer: "_TZ3000_rco1yzb1", model: "TS004F", deviceJoinName: "LIDL Smart Button SSBM A1"
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0042", manufacturer:"_TZ3000_tzvbimpq", deviceJoinName: "Tuya 2 button Scene Switch"
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0042", manufacturer:"_TZ3000_t8hzpgnd", deviceJoinName: "Tuya 2 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0042", manufacturer:"_TYZB02_keyjhapk", deviceJoinName: "Tuya 2 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0042", manufacturer:"_TZ3400_keyjhapk", deviceJoinName: "Tuya 2 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0042", manufacturer:"_TZ3000_dfgbtub0", deviceJoinName: "Tuya 2 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0042", manufacturer:"_TZ3000_h1c2eamp", deviceJoinName: "Tuya 2 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0042", manufacturer:"_TZ3000_owgcnkrh", deviceJoinName: "Tuya 2 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0042", manufacturer:"_TZ3000_tzvbimpq", deviceJoinName: "Tuya 2 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0042", manufacturer:"_TZ3000_fkvaniuu", deviceJoinName: "Tuya 2 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters: "0000,0001,0006", outClusters: "0019", manufacturer: "_TYZB02_key8kk7r", model: "TS0042", deviceJoinName: "Tuya 2 button Scene Switch"
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0006", outClusters:"0019,000A", model:"TS0043", manufacturer:"_TZ3000_w8jwkczz", deviceJoinName: "Tuya 3 button Scene Switch"
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0006", outClusters:"0019,000A", model:"TS0043", manufacturer:"_TZ3000_gbm10jnj", deviceJoinName: "Tuya Zigbee 3 button" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0006", outClusters:"0019,000A", model:"TS0043", manufacturer:"_TYZB02_key8kk7r", deviceJoinName: "Tuya 3 button Scene Switch"
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0006", outClusters:"0019,000A", model:"TS0043", manufacturer:"_TZ3000_qzjcsmar", deviceJoinName: "Tuya 3 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,000A,0001,0006", outClusters:"0019", model:"TS0043", manufacturer:"_TZ3000_bi6lpsew", deviceJoinName: "Tuya 3 button Scene Switch"
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0006", outClusters:"0019,000A", model:"TS0043", manufacturer:"_TZ3000_imnwsek2", deviceJoinName: "Tuya 3 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0006", outClusters:"0019,000A", model:"TS0043", manufacturer:"_TZ3000_rrjr1q0u", deviceJoinName: "Tuya 3 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0006", outClusters:"0019,000A", model:"TS0043", manufacturer:"_TZ3000_w4thianr", deviceJoinName: "Tuya 3 button Scene Switch" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0006", outClusters:"0019,000A", model:"TS0043", manufacturer:"_TZ3000_a7ouggvs", deviceJoinName: "Zigbee Lonsonho 3 Button" // not tested
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0006", outClusters:"0019,000A", model:"TS0043", manufacturer:"_TZ3000_famkxci2", deviceJoinName: "Loratap 3 Button rRemote" // https://community.hubitat.com/t/zigbee-3-switch-remote-driver/111935/3?u=kkossev
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0006", outClusters:"0019,000A", model:"TS0044", manufacturer:"_TZ3000_vp6clf9d", deviceJoinName: "Zemismart Wireless Scene Switch"
fingerprint inClusters: "0000,000A,0001,0006", outClusters: "0019", manufacturer: "_TZ3000_vp6clf9d", model: "TS0044", deviceJoinName: "Zemismart 4 Button Remote (ESW-0ZAA-EU)" // needs debouncing
fingerprint inClusters: "0000,000A,0001,0006", outClusters: "0019", manufacturer: "_TZ3000_ufhtxr59", model: "TS0044", deviceJoinName: "Tuya 4 button Scene Switch"
fingerprint inClusters: "0000,000A,0001,0006", outClusters: "0019", manufacturer: "_TZ3000_wkai4ga5", model: "TS0044", deviceJoinName: "Tuya 4 button Scene Switch" // https://community.hubitat.com/t/release-tuya-scene-switch-ts004f-driver/92823/79?u=kkossev
fingerprint inClusters: "0000,000A,0001,0006", outClusters: "0019", manufacturer: "_TZ3000_abci1hiu", model: "TS0044", deviceJoinName: "Tuya 4 button Scene Switch" // not tested
fingerprint inClusters: "0000,000A,0001,0006", outClusters: "0019", manufacturer: "_TZ3000_ee8nrt2l", model: "TS0044", deviceJoinName: "Tuya 4 button Scene Switch" // not tested
fingerprint inClusters: "0000,000A,0001,0006", outClusters: "0019", manufacturer: "_TZ3000_dku2cfsc", model: "TS0044", deviceJoinName: "Tuya 4 button Scene Switch" // not tested
fingerprint inClusters: "0000,000A,0001,0006", outClusters: "0019", manufacturer: "_TYZB01_cnlmkhbk", model: "TS0044", deviceJoinName: "Tuya 4 button Scene Switch" // not tested
fingerprint inClusters: "0000,0001,0006", outClusters: "0019,000A", manufacturer: "_TZ3000_u3nv1jwk", model: "TS0044", deviceJoinName: "Tuya 4 button Scene Switch" // not tested https://community.hubitat.com/t/zigbee-wireless-scene-switch/108146?u=kkossev
fingerprint profileId: "0104", endpointId: "01", inClusters: "0001,0006,E000,0000", outClusters: "0019,000A", model: "TS0044", manufacturer: "_TZ3000_mh9px7cq", deviceJoinName: "Moes 4 button controller" // https://community.hubitat.com/t/release-tuya-scene-switch-ts004f-driver/92823/75?u=kkossev
fingerprint inClusters: "0000,0001,0003,0004,0006,1000", outClusters: "0019,000A,0003,0004,0005,0006,0008,1000", manufacturer: "_TZ3000_abci1hiu", model: "TS0044", deviceJoinName: "MOES Remote TS0044"
fingerprint profileId: "0104", endpointId:"01", inClusters:"0004,0005,EF00,0000", outClusters:"0019,000A", model:"TS0601", manufacturer:"_TZE200_2m38mh6k", deviceJoinName: "LoraTap 6 button Scene Switch"
fingerprint profileId: "0104", endpointId:"01", inClusters:"0004,0005,EF00,0000", outClusters:"0019,000A", model:"TS0601", manufacturer:"_TZE200_zqtiam4u", deviceJoinName: "Tuya 6 button Scene Switch" // not tested
fingerprint profileId: "0104", endpointId:"01", inClusters:"0004,0005,EF00,0000", outClusters:"0019,000A", model:"TS0046", manufacturer:"_TZ3000_iszegwpd", deviceJoinName: "LoraTap 6 Scene Switch" // https://community.hubitat.com/t/loratap-6-button-controller-drivers/91951/20?u=kkossev
fingerprint profileId: "0104", endpointId:"01", inClusters:"0001,0006,E000,0000", outClusters:"0019,000A", model:"TS0046", manufacturer:"_TZ3000_iszegwpd", deviceJoinName: "LoraTap 6 Scene Switch" // https://user-images.githubusercontent.com/42491156/210933986-16fc9854-b7d8-4239-a6ef-311ba631e480.png
fingerprint profileId: "0104", endpointId: "01", inClusters: "0000,0001,0003,0B05,1000", outClusters: "0003,0004,0005,0006,0008,0019,0300,1000", model:"ICZB-KPD18S", manufacturer:"icasa", deviceJoinName: "Icasa 8 button Scene Switch" //https://community.hubitat.com/t/beginners-question-fantastic-button-controller-not-working/103914
fingerprint profileId: "0104", endpointId: "01", inClusters: "0000,0001,0003,0006,FCC0", outClusters: "0003,FCC0", model: "3AFE280100510001", manufacturer: "Konke", deviceJoinName: "Konke button" // sends Voltage (only!) every 2 hours
fingerprint profileId: "0104", endpointId: "01", inClusters: "0000,0001,0003,0004,0005,0006", outClusters: "0003", model: "3AFE170100510001", manufacturer: "Konke", deviceJoinName: "Konke button"
fingerprint profileId: "0104", endpointId: "01", inClusters: "0000,0003,0001", outClusters: "0006,0003", model: "WB01", manufacturer: "eWeLink", deviceJoinName: "Sonoff SNZB-01 button"
fingerprint profileId: "0104", endpointId: "01", inClusters: "0000,0003,0001", outClusters: "0006,0003", model: "WB-01", manufacturer: "eWeLink", deviceJoinName: "Sonoff SNZB-01 button"
fingerprint profileId: "0104", endpointId: "01", inClusters: "0000,0020,0001,0003,FC57", outClusters: "0003,0006,0019", model: "SNZB-01P", manufacturer: "eWeLink", deviceJoinName: "Sonoff SNZB-01 button"
fingerprint profileId: "0104", endpointId: "01", inClusters: "0000,0001,0003,0009,0020,1000", outClusters:"0003,0004,0006,0008,0019,0102,1000", model:"TRADFRI SHORTCUT Button", manufacturer:"IKEA of Sweden", deviceJoinName: "IKEA Tradfri Shortcut Button E1812"
// OSRAM Lightify - use HE inbuilt driver to pair first !
//fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0020,1000,FD00", outClusters:"0003,0004,0005,0006,0008,0019,0300,1000", model:"Lightify Switch Mini", manufacturer:"OSRAM", deviceJoinName: "Lightify Switch Mini"
// 4 button
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0003,0500,0B05", outClusters:"0019,0501", model:"TS0215", manufacturer:"_TYZB01_qm6djpta", deviceJoinName: "Tuya 4 Key Arm Disarm Home SOS Button" // https://www.aliexpress.com/item/4001062612446.html KK
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0003,0500,0B05", outClusters:"0019,0501", model:"TS0215", manufacturer:"_TZ3000_fsiepnrh", deviceJoinName: "4 Button Smart Remote Controller"
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0003,0500,0B05", outClusters:"0019,0501", model:"TS0215", manufacturer:"_TZ3000_p6ju8myv", deviceJoinName: "4 Button Smart Remote Controller"
// 4 button Security remote control (cluster: 'ssIasAce') = command_arm, command_emergency; ['disarm', 'arm_day_zones', 'arm_night_zones', 'arm_all_zones', 'exit_delay', 'emergency']
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0500,0501", outClusters: "0019,000A", model: "TS0215A", manufacturer: "_TZ3000_fsiepnrh", deviceJoinName: "Nedis Zigbee 4 Button Fob"
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0500,0501", outClusters: "0019,000A", model: "TS0215A", manufacturer: "_TZ3000_ug1vtuzn", deviceJoinName: "Tuya Security remote control" // - 1 button ???
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0500,0501", outClusters: "0019,000A", model: "TS0215A", manufacturer: "_TZ3000_0zrccfgx", deviceJoinName: "Tuya Security remote control"
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0500,0501", outClusters: "0019,000A", model: "TS0215A", manufacturer: "_TZ3000_p6ju8myv", deviceJoinName: "Tuya Security remote control"
// SOS 1 button - command_emergency
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0500,0501", outClusters: "0019,000A", model: "TS0215A", manufacturer: "_TZ3000_4fsgukof", deviceJoinName: "Tuya SOS button" // 1 button
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0500,0501", outClusters: "0019,000A", model: "TS0215A", manufacturer: "_TZ3000_wr2ucaj9", deviceJoinName: "Tuya SOS button" // 1 button
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0500,0501", outClusters: "0019,000A", model: "TS0215A", manufacturer: "_TZ3000_zsh6uat3", deviceJoinName: "Tuya SOS button" // 1 button
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0500,0501", outClusters: "0019,000A", model: "TS0215A", manufacturer: "_TZ3000_tj4pwzzm", deviceJoinName: "Tuya SOS button"
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0003,0500,0000", outClusters: "0019,000A", model: "TS0215A", manufacturer: "_TZ3000_2izubafb", deviceJoinName: "Tuya SOS button" // @abraham
fingerprint profileId:"0104", endpointId:"01", inClusters:"0001,0003,0500,0000", outClusters: "0501,0019,000A", model: "TS0215A", manufacturer: "_TZ3000_pkfazisv", deviceJoinName: "iAlarm (Meian) SOS button" // https://community.hubitat.com/t/request-adding-fingerprints-for-ialarm-devices/118166/2?u=kkossev
}
preferences {
input (name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: DEFAULT_LOG_ENABLE)
input (name: "txtEnable", type: "bool", title: "Enable description text logging", defaultValue: true)
input (name: "reverseButton", type: "bool", title: "Reverse button order", defaultValue: true)
input (name: "advancedOptions", type: "bool", title: "Advanced options", defaultValue: false)
if (advancedOptions == true) {
input name: 'batteryReporting', type: 'enum', title: 'Battery Reporting Interval', options: batteryReportingOptions.options, defaultValue: batteryReportingOptions.defaultValue, description: \
'Keep the battery reporting interval to Default, except when battery level is not reported at all for a long period.
Caution:some devices are repored to deplete the battery very fast, if the battery reporting is set different than the default!'
}
}
}
// Constants
@Field static final Integer DIMMER_MODE = 0
@Field static final Integer SCENE_MODE = 1
@Field static final Integer DEBOUNCE_TIME = 1000
@Field static final Boolean DEFAULT_LOG_ENABLE = true
@Field static final Map batteryReportingOptions = [
defaultValue: 00,
options : [00: 'Default', 14400: 'Every 4 Hours', 28800: 'Every 8 Hours', 43200: 'Every 12 Hours', 86400: 'Every 24 Hours']
]
def isTuya() {device.getDataValue("model") in ["TS0601", "TS004F", "TS0044", "TS0043", "TS0042", "TS0041", "TS0046", "TS0215", "TS0215A"]}
def isIcasa() {device.getDataValue("manufacturer") == "icasa"}
def isSmartKnob() {device.getDataValue("manufacturer") in ["_TZ3000_4fjiwweb", "_TZ3000_rco1yzb1", "_TZ3000_uri7ongn", "_TZ3000_ixla93vd", "_TZ3000_qja6nq5z", "_TZ3000_csflgqj2", "_TZ3000_abrsvsou"]}
def isKonkeButton() {device.getDataValue("model") in ["3AFE280100510001", "3AFE170100510001"]}
def isSonoff() {device.getDataValue("manufacturer") == "eWeLink"}
def isIkea() {device.getDataValue("manufacturer") == "IKEA of Sweden"}
def isOsram() {device.getDataValue("manufacturer") == "OSRAM"}
def needsDebouncing() {device.getDataValue("model") == "TS004F" || (device.getDataValue("manufacturer") in ["_TZ3000_abci1hiu", "_TZ3000_vp6clf9d", "_TZ3000_ur5fpg7p"])}
def needsMagic() {device.getDataValue("model") in ["TS004F", "TS0044", "TS0043", "TS0042", "TS0041", "TS0046"]}
def isSOSbutton() {device.getDataValue("manufacturer") in ["_TZ3000_4fsgukof", "_TZ3000_wr2ucaj9", "_TZ3000_zsh6uat3", "_TZ3000_tj4pwzzm", "_TZ3000_2izubafb", "_TZ3000_pkfazisv" ]}
// Parse incoming device messages to generate events
def parse(String description) {
checkDriverVersion()
if (state.stats != null) {state.stats["rxCtr"] = (state.stats["rxCtr"] ?: 0) + 1}
setHealthStatusOnline()
if (logEnable) log.debug "${device.displayName} description is $description"
def event = null
try {
event = zigbee.getEvent(description)
}
catch (e) {
if (logEnable) {log.warn "${device.displayName} exception caught while procesing event $description"}
}
def result = []
def buttonNumber = 0
if (event) {
if (logEnable) log.debug "${device.displayName} Event enter: $event"
switch (event.name) {
case 'battery' :
event.value = event.value as int // HomeKit
event.unit = '%'
break
case 'batteryVoltage' :
event.unit = 'V'
break
case 'switch' : // Konke button
if (isKonkeButton()) {
processKonkeButton(description)
return null
}
break
default :
if (logEnable) log.debug "${device.displayName} Unexpected event: $event"
break
}
event.descriptionText = "${event.name} is ${event.value} ${event.unit}"
event.isStateChange = true
event.type = 'physical'
if (txtEnable) log.info "${device.displayName} ${event.descriptionText}"
result = event
}
else if (description?.startsWith("catchall")) {
def descMap = zigbee.parseDescriptionAsMap(description)
if (logEnable) log.debug "${device.displayName} catchall descMap: $descMap"
def buttonState = "unknown"
// when TS004F initialized in Scene switch mode!
if (descMap.clusterInt == 0x0006 && descMap.command == "FD") {
if (descMap.sourceEndpoint == "03") {
buttonNumber = reverseButton==true ? 3 : 1
}
else if (descMap.sourceEndpoint == "04") {
buttonNumber = reverseButton==true ? 4 : 2
}
else if (descMap.sourceEndpoint == "02") {
buttonNumber = reverseButton==true ? 2 : 3
}
else if (descMap.sourceEndpoint == "01") {
buttonNumber = reverseButton==true ? 1 : 4
}
else if (descMap.sourceEndpoint == "05") { // LoraTap TS0046
buttonNumber = reverseButton==true ? 5 : 5
}
else if (descMap.sourceEndpoint == "06") {
buttonNumber = reverseButton==true ? 6 : 6
}
if (descMap.data[0] == "00")
buttonState = "pushed"
else if (descMap.data[0] == "01")
buttonState = "doubleTapped"
else if (descMap.data[0] == "02")
buttonState = "held"
else {
if (logEnable) {log.warn "${device.displayName} unkknown data in event from cluster ${descMap.clusterInt} sourceEndpoint ${descMap.sourceEndpoint} data[0] = ${descMap.data[0]}"}
return null
}
} // command == "FD"
else if (isSonoff() && (descMap.clusterInt == 0x0006 && (descMap.command in ["00","01","02" ]))) {
// Sonoff SNZB-01
buttonNumber = 1
buttonState = descMap.command == "02" ? "pushed" : descMap.command == "01" ? "doubleTapped" : descMap.command == "00" ? "held" : "unknown"
}
else if (isIkea() && ((descMap.clusterInt == 0x0006 || descMap.clusterInt == 0x0008) && (descMap.command in ["01","05","07" ]))) {
// IKEA Tradfri Shortcut Button E1812
buttonNumber = 1
if (descMap.clusterInt == 0x0006 && descMap.command == "01") buttonState = "pushed"
else if (descMap.clusterInt == 0x0008 && descMap.command == "05") buttonState = "held"
else if (descMap.clusterInt == 0x0008 && descMap.command == "07") buttonState = "released"
else buttonState = "unknown"
}
else if (isOsram() && ((descMap.clusterInt == 0x0006 || descMap.clusterInt == 0x0008) && (descMap.command in ["01","03","05","04","00" ]))) {
// OSRAM Lightify Mini
buttonNumber = safeToInt(descMap.sourceEndpoint)
if (descMap.command == "01") buttonState = "pushed"
else if (descMap.command == "04") buttonState = "pushed"
else if (descMap.command == "00") buttonState = "pushed"
else if (descMap.command == "05") buttonState = "held"
else if (descMap.command == "03") buttonState = "released"
else buttonState = "unknown"
}
else if (descMap.clusterInt == 0x0501) {
// TODO: Make the button numbers compatible with Muxa's driver : 1 - Arm Away (left); 2 - Disarm (right); 3 - Arm Home (top); 4 - Panic (bottom) // https://community.hubitat.com/t/release-heiman-zigbee-key-fob-driver/27002
if (descMap.command == "02" && descMap.data.size() == 0) {
buttonNumber = reverseButton == true ? 1 : 3
}
else if (descMap.command == "00" && descMap.data.size() >= 1) {
if (descMap.data[0] == "03") {
buttonNumber = reverseButton == true ? 2 : 4
}
else if (descMap.data[0] == "01") {
buttonNumber = reverseButton == true ? 3 : 1
}
else if (descMap.data[0] == "00") {
buttonNumber = reverseButton == true ? 4 : 2
}
}
if (buttonNumber != 0 ) {
buttonState = "pushed"
}
else {
if (logEnable) {log.warn "${device.displayName} unkknown event from cluster=${descMap.clusterInt} command=${descMap.command} data=${descMap?.data}"}
return null
}
}
else if (descMap.clusterInt == 0x0006 && descMap.command == "FC") {
// Smart knob
if (descMap.data[0] == "00") { // Rotate one click right
buttonNumber = 2
}
else if (descMap.data[0] == "01") { // Rotate one click left
buttonNumber = 3
}
buttonState = "pushed"
}
else if (descMap.clusterId in ['0000', '0006', 'E001'] && descMap.command == "01" && descMap.data?.size() >=3) { // read attribute response
if (descMap.data[2] == '86') {
if (logEnable) log.debug "${device.displayName} readAttributeResponse cluster: ${descMap.clusterId} unsupported attribute ${descMap.data[1]+descMap.data[0]} status:${descMap.data[2]}"
}
else {
if (logEnable) log.debug "${device.displayName} readAttributeResponse cluster: ${descMap.clusterId} ${descMap.data[1]+descMap.data[0]} status:${descMap.data[2]} value:${descMap?.value}"
}
return null
}
else if (descMap.clusterInt == 0x0006 && descMap.command == "04") { // write attribute response
if (logEnable) log.debug "${device.displayName} writeAttributeResponse cluster: ${descMap.clusterId} status:${descMap.data[0]}"
return null
}
else if (descMap?.profileId == '0000' && descMap?.clusterId == '0013') { // device announcement
logInfo "received device announcement, Device network ID: ${descMap.data[2]+descMap.data[1]}"
state.stats["rejoinCtr"] = (state.stats["rejoinCtr"] ?: 0) + 1
return null
}
else if (descMap?.profileId == '0000' && descMap?.clusterId == '8021') { // bind response
logInfo "received bind response, data=${descMap.data} (Sequence Number:${descMap.data[0]}, Status: ${descMap.data[1]=="00" ? 'Success' : 'Failure'})"
return null
}
else if (descMap?.profileId == '0000' && descMap?.clusterId == '8034') { // leave response
logInfo "leave response cluster: ${descMap.clusterId}"
return null
}
// TODO: (on pairing new device) : Zigbee parsed:[raw:catchall: 0000 8005 00 00 0040 00 0F4B 00 00 0000 00 00 3B004B0F0101, profileId:0000, clusterId:8005, clusterInt:32773, sourceEndpoint:00, destinationEndpoint:00, options:0040, messageType:00, dni:0F4B, isClusterSpecific:false, isManufacturerSpecific:false, manufacturerId:0000, command:00, direction:00, data:[3B, 00, 4B, 0F, 01, 01]]
else if (descMap.clusterId == "EF00" && descMap.command == "01") { // check for LoraTap button events
if (descMap.data.size() == 10 && descMap.data[2] == "0A" ) {
def value = zigbee.convertHexToInt(descMap?.data[9])
def descText = "battery is ${value} %"
if (txtEnable) log.info "${device.displayName} ${descText}"
return createEvent(name: "battery", value: value, unit: '%', descriptionText: descText, type: 'physical')
}
else if (descMap.data.size() == 7 && descMap.data[2] >= "01" && descMap.data[2] <= "06") {
buttonNumber = zigbee.convertHexToInt(descMap.data[2])
if (descMap.data[6] == "00")
buttonState = "pushed"
else if (descMap.data[6] == "01")
buttonState = "doubleTapped"
else if (descMap.data[6] == "02")
buttonState = "held"
}
else {
if (logEnable) {log.debug "${device.displayName} unprocessed Tuya cluster EF00 command descMap: $descMap"}
}
}
else if (isIcasa()) {
(buttonNumber,buttonState) = processIcasa( descMap )
}
else {
if (logEnable) {log.debug "${device.displayName} unprocessed catchall from cluster ${descMap.clusterInt} sourceEndpoint ${descMap.sourceEndpoint}"}
if (logEnable) {log.debug "${device.displayName} catchall descMap: $descMap"}
}
//
if (buttonNumber != 0 ) {
if (needsDebouncing()) {
if ( state.lastButtonNumber == buttonNumber ) { // debouncing timer still active!
if (logEnable) {log.warn "${device.displayName} ignored event for button ${state.lastButtonNumber} - still in the debouncing time period!"}
runInMillis(DEBOUNCE_TIME, buttonDebounce, [overwrite: true]) // restart the debouncing timer again
if (logEnable) {log.debug "${device.displayName} restarted debouncing timer ${DEBOUNCE_TIME}ms for button ${buttonNumber} (lastButtonNumber=${state.lastButtonNumber})"}
return null
}
}
state.lastButtonNumber = buttonNumber
}
else {
if (logEnable) {log.warn "${device.displayName} UNHANDLED event for button ${buttonNumber}, lastButtonNumber=${state.lastButtonNumber}"}
}
if (buttonState != "unknown" && buttonNumber != 0) {
def descriptionText = "button $buttonNumber was $buttonState"
event = [name: buttonState, value: buttonNumber.toString(), data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true, type: 'physical']
if (txtEnable) {log.info "${device.displayName} $descriptionText"}
}
if (event) {
result = createEvent(event)
if (device.getDataValue("model") == "TS004F" || device.getDataValue("manufacturer") == "_TZ3000_abci1hiu") {
runInMillis(DEBOUNCE_TIME, buttonDebounce, [overwrite: true])
}
}
} // if catchall
else {
def descMap = zigbee.parseDescriptionAsMap(description)
if (logEnable) log.debug "${device.displayName} raw: descMap: $descMap"
//log.trace "${device.displayName} descMap.cluster=${descMap.cluster} descMap.attrId=${descMap.attrId} descMap.command=${descMap.command} "
if (descMap.cluster == "0006" && descMap.attrId == "8004") {
if (descMap.value == "00") {
sendEvent(name: "switchMode", value: "dimmer", isStateChange: true)
if (txtEnable) log.info "${device.displayName} mode is dimmer"
}
else if (descMap.value == "01") {
sendEvent(name: "switchMode", value: "scene", isStateChange: true)
if (txtEnable) log.info "${device.displayName} mode is scene"
}
else {
if (logEnable) log.warn "${device.displayName} unknown attrId ${descMap.attrId} value ${descMap.value}"
}
}
else if (descMap?.cluster == '0000' && descMap?.command in ['01'] ) { // Basic Cluster responses
if (logEnable) log.debug "${device.displayName} skipping Basic cluster ${descMap?.cluster} response"
return null
}
else {
if (logEnable) {log.debug "${device.displayName} did not parse descMap: $descMap"}
}
}
return result
}
@Field static final Integer BUTTON_I = 8
@Field static final Integer BUTTON_O = 7
def processIcasa( descMap ) {
buttonNumber = 0
buttonState = "unknown"
//log.trace "descMap=${descMap}"
if (descMap?.clusterId == "0006") {
switch (descMap?.command) {
case "00" : // button "O" -> 7
buttonNumber = BUTTON_O
buttonState = "pushed"
break
case "01" : // pushed button "I" -> 8
buttonNumber = BUTTON_I
buttonState = "pushed"
break
default :
if (logEnable) log.warn "${device.displayName} unprocessed ICASA cluster 0006 command ${descMap?.command}"
}
}
else if (descMap?.clusterId == "0008") {
switch (descMap?.command) {
case "05" : // HELD for both buttons I and O
if (descMap?.data[0] == '00') buttonNumber = BUTTON_I
else if (descMap?.data[0] == '01') buttonNumber = BUTTON_O
else {if (logEnable) log.warn "${device.displayName} unprocessed ICASA cluster 0008 HOLD command data=${descMap?.data}" }
buttonState = "held"
break
case "07" : // RELEASE after HOLD for both buttons I and O
buttonNumber = state.lastButtonNumber
buttonState = "released"
if (logEnable) log.debug "${device.displayName} generating release for state.lastButtonNumber ${state.lastButtonNumber}"
break
default :
if (logEnable) log.warn "${device.displayName} unprocessed ICASA cluster 0008 command ${descMap?.command}"
}
}
else if (descMap?.clusterId == "0005") {
switch (descMap?.command) {
case "05" : // CLICK for buttons S1..S6
buttonNumber = zigbee.convertHexToInt(descMap?.data[2])
buttonState = "pushed"
break
case "04" : // HELD for buttons S1..S6
buttonNumber = zigbee.convertHexToInt(descMap?.data[2])
buttonState = "held"
break
default :
if (logEnable) log.warn "${device.displayName} unprocessed ICASA cluster 0005 command ${descMap?.command}"
}
}
else {
if (logEnable) log.warn "${device.displayName} unprocessed ICASA cluster ${decMap?.clusterId} message ${descMap}"
}
return [buttonNumber, buttonState]
}
def processKonkeButton( description ) {
def buttonNumber = 0
def buttonState = "unknown"
def descMap = zigbee.parseDescriptionAsMap(description)
if (logEnable) log.debug "${device.displayName} KonkeButton descMap: $descMap"
if (descMap.cluster != "0006" ) {
return
}
buttonNumber = 1
if (descMap.value == "80") {
buttonState = "pushed"
}
else if (descMap.value == "81") {
buttonState = "doubleTapped"
}
else if (descMap.value == "82") {
buttonState = "held"
}
else if (descMap.value == "CD") {
logInfo "${device.displayName} KonkeButton reset/pair button was pressed"
return
}
else {
return
}
state.lastButtonNumber = buttonNumber
buttonEvent(buttonNumber, buttonState)
return
}
def refresh() {
}
def driverVersionAndTimeStamp() {version()+' '+timeStamp()}
def checkDriverVersion() {
if (state.driverVersion == null || driverVersionAndTimeStamp() != state.driverVersion) {
if (txtEnable==true) log.debug "${device.displayName} updating the settings from the current driver version ${(state.driverVersion ?: 'UNKNOWN')} to the new version ${driverVersionAndTimeStamp()}"
initializeVars( fullInit = false )
scheduleDeviceHealthCheck()
state.driverVersion = driverVersionAndTimeStamp()
}
}
void initializeVars(boolean fullInit = false ) {
if (settings?.txtEnable) log.info "${device.displayName} InitializeVars()... fullInit = ${fullInit}"
if (fullInit == true ) {
state.clear()
state.driverVersion = driverVersionAndTimeStamp()
state.stats = [:]
}
if (state.stats == null) { state.stats = [:] }
state.comment = "Works with Tuya TS004F TS0041 TS0042 TS0043 TS0044 TS0046 TS0601, icasa, Konke, Sonoff"
if (fullInit == true || settings?.logEnable == null) device.updateSetting("logEnable", DEFAULT_LOG_ENABLE)
if (fullInit == true || settings?.txtEnable == null) device.updateSetting("txtEnable", true)
if (fullInit == true || settings?.reverseButton == null) device.updateSetting("reverseButton", true)
if (fullInit == true || settings?.advancedOptions == null) device.updateSetting("advancedOptions", false)
if (fullInit == true || state.notPresentCounter == null) state.notPresentCounter = 0
}
def configure() {
if (logEnable) log.debug "${device.displayName} Configuring device model ${device.getDataValue("model")} manufacturer ${device.getDataValue('manufacturer')} ..."
initialize()
}
def installed()
{
logInfo "installed()..."
initializeVars( fullInit = true )
initialize()
}
def initialize() {
if (/*true*/ isTuya()) {
tuyaMagic()
}
else if (isSonoff()) {
sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} ${device.endpointId} 0x01 0x0006 {${device.zigbeeId}} {}", "delay 50", ])
sendZigbeeCommands(["he rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x0001 0x0021 {}", "delay 200", ])
}
else if (isOsram()) {
sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x01 0x01 0x0006 {${device.zigbeeId}} {}", "delay 50", ])
sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x01 0x01 0x0008 {${device.zigbeeId}} {}", "delay 50", ])
sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x01 0x01 0x0300 {${device.zigbeeId}} {}", "delay 50", ])
sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x02 0x01 0x0006 {${device.zigbeeId}} {}", "delay 50", ])
sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x02 0x01 0x0008 {${device.zigbeeId}} {}", "delay 50", ])
sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x02 0x01 0x0300 {${device.zigbeeId}} {}", "delay 50", ])
sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x03 0x01 0x0006 {${device.zigbeeId}} {}", "delay 50", ])
sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x03 0x01 0x0008 {${device.zigbeeId}} {}", "delay 50", ])
sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x03 0x01 0x0300 {${device.zigbeeId}} {}", "delay 50", ])
//sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x04 0x01 0x0006 {${device.zigbeeId}} {}", "delay 50", ])
//sendZigbeeCommands(["zdo bind ${device.deviceNetworkId} 0x04 0x01 0x0008 {${device.zigbeeId}} {}", "delay 50", ])
}
else {
if (logEnable) log.debug "${device.displayName} skipped TuyaMagic() for non-Tuya device ${device.getDataValue("model")} ..."
}
// determine the number of the buttons and the supported button actions (values) depending on the model/manufactuer
def numberOfButtons = 4
def supportedValues = ["pushed", "double", "held"]
if (isSOSbutton()) {
numberOfButtons = 1
supportedValues = ["pushed"]
}
else if ((device.getDataValue("model") in ["TS0041", "3AFE280100510001", "3AFE170100510001"]) || (device.getDataValue("manufacturer") in ["_TZ3000_ja5osu5g", "eWeLink"])) {
numberOfButtons = 1
}
else if (device.getDataValue("model") == "TS0042") {
numberOfButtons = 2
}
else if (device.getDataValue("model") == "TS0043") {
numberOfButtons = 3
}
else if (device.getDataValue("model") == "TS004F" || device.getDataValue("model") == "TS0044") {
if (isSmartKnob()) { // Smart Knob
log.debug "${device.displayName} device ${device.data.manufacturer} identified as Smart Knob model ${device.data.model}"
numberOfButtons = 3
supportedValues = ["pushed", "double", "held", "released"]
}
else {
log.debug "${device.displayName} device ${device.data.manufacturer} identified as 4 keys scene switch model ${device.data.model}"
numberOfButtons = 4
supportedValues = ["pushed", "double", "held"] // no released events are generated in scene switch mode
}
}
else if (device.getDataValue("model") == "TS0215") {
numberOfButtons = 4
supportedValues = ["pushed"]
}
else if (device.getDataValue("model") == "TS0045") { // just in case a new Tuya devices manufacturer decides to invent a new model! :)
numberOfButtons = 5
}
else if (device.getDataValue("model") in ["TS0601", "TS0046"]) {
numberOfButtons = 6
}
else if (isIcasa()) {
numberOfButtons = 8
supportedValues = ["pushed", "held", "released"]
}
else if (isIkea()) {
numberOfButtons = 1
supportedValues = ["pushed", "held", "released"]
}
else if (isOsram()) { // mini
numberOfButtons = 3
supportedValues = ["pushed", "held", "released"]
}
else {
numberOfButtons = 4 // unknown
supportedValues = ["pushed", "double", "held", "released"]
log.warn "${device.displayName} unknown device model ${device.getDataValue('model')} manufacturer ${device.getDataValue('manufacturer')}. Please report this log to the developer."
}
sendEvent(name: "numberOfButtons", value: numberOfButtons, isStateChange: true)
sendEvent(name: "supportedButtonValues", value: JsonOutput.toJson(supportedValues), isStateChange: true)
if(device.currentValue('healthStatus') == null) setHealthStatusValue('unknown')
if(device.currentValue('powerSource') == null) sendEvent(name: "powerSource", value: "battery", isStateChange: true)
state.lastButtonNumber = 0
scheduleDeviceHealthCheck()
}
def updated()
{
if (logEnable) {log.debug "${device.displayName} updated()"}
scheduleDeviceHealthCheck()
}
def buttonDebounce(button) {
if (logEnable) log.debug "${device.displayName} debouncing timer for button ${state.lastButtonNumber} expired."
state.lastButtonNumber = 0
}
def switchToSceneMode()
{
if (logEnable) log.debug "${device.displayName} Switching TS004F into Scene mode"
sendZigbeeCommands(zigbee.writeAttribute(0x0006, 0x8004, 0x30, 0x01))
}
def switchToDimmerMode()
{
if (logEnable) log.debug "${device.displayName} Switching TS004F into Dimmer mode"
sendZigbeeCommands(zigbee.writeAttribute(0x0006, 0x8004, 0x30, 0x00))
}
def buttonEvent(buttonNumber, buttonState, isDigital=false) {
def event = [name: buttonState, value: buttonNumber.toString(), data: [buttonNumber: buttonNumber], descriptionText: "button $buttonNumber was $buttonState", isStateChange: true, type: isDigital==true ? 'digital' : 'physical']
if (txtEnable) {log.info "${device.displayName} $event.descriptionText"}
sendEvent(event)
}
def push(buttonNumber) {
buttonEvent(buttonNumber, "pushed", isDigital=true)
}
def doubleTap(buttonNumber) {
buttonEvent(buttonNumber, "doubleTapped", isDigital=true)
}
def hold(buttonNumber) {
buttonEvent(buttonNumber, "held", isDigital=true)
}
def release(buttonNumber) {
buttonEvent(buttonNumber, "released", isDigital=true)
}
def switchMode( mode ) {
if (mode == "dimmer") {
switchToDimmerMode()
}
else if (mode == "scene") {
switchToSceneMode()
}
}
Integer safeToInt(val, Integer defaultVal=0) {
return "${val}"?.isInteger() ? "${val}".toInteger() : defaultVal
}
Double safeToDouble(val, Double defaultVal=0.0) {
return "${val}"?.isDouble() ? "${val}".toDouble() : defaultVal
}
def tuyaMagic() {
ArrayList cmd = []
cmd += zigbee.readAttribute(0x0000, [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe], [:], delay=200) // Cluster: Basic, attributes: Man.name, ZLC ver, App ver, Model Id, Power Source, Unknown 0xfffe
/*
cmd += "raw 0x0000 {10 00 00 04 00 00 00 01 00 05 00 07 00 FE FF}"
cmd += "send 0x${device.deviceNetworkId} 1 255"
cmd += "delay 200"
*/
if (needsMagic()) {
cmd += zigbee.readAttribute(0x0006, 0x8004, [:], delay=50) // success / 0x00
cmd += zigbee.readAttribute(0xE001, 0xD011, [:], delay=50) // Unsupported attribute (0x86)
cmd += zigbee.readAttribute(0x0001, [0x0020, 0x0021], [:], delay=50) // Battery voltage + Battery Percentage Remaining
cmd += zigbee.writeAttribute(0x0006, 0x8004, 0x30, 0x01, [:], delay=50) // switch into Scene Mode !
cmd += zigbee.readAttribute(0x0006, 0x8004, [:], delay=50)
}
// binding for battery reporting was added on 2023/01/04 (ver 2.5.0), but thee are doubts that it may cause device re-joins and depletes the battery!
int batteryReportinginterval = (settings.batteryReporting as Integer) ?: 0
if (batteryReportinginterval > 0) {
logInfo "setting the battery reporting interval to ${(batteryReportinginterval/3600) as int} hours"
cmd += zigbee.configureReporting(0x0001, 0x0020, DataType.UINT8, 600, batteryReportinginterval, 0x01, [:], delay=150)
cmd += zigbee.configureReporting(0x0001, 0x0021, DataType.UINT8, 600, batteryReportinginterval, 0x01, [:], delay=150) // 0x21 is NOT supported by all devices?
}
else {
logInfo "battery reporting interval not changed."
}
sendZigbeeCommands(cmd)
}
void sendZigbeeCommands(ArrayList cmd) {
if (logEnable) {log.trace "${device.displayName} sendZigbeeCommands(cmd=$cmd)"}
hubitat.device.HubMultiAction allActions = new hubitat.device.HubMultiAction()
cmd.each {
allActions.add(new hubitat.device.HubAction(it, hubitat.device.Protocol.ZIGBEE))
}
if (state.stats != null) {state.stats["txCtr"] = (state.stats["txCtr"] ?: 0) + 1 }
sendHubCommand(allActions)
}
void scheduleDeviceHealthCheck() {
Random rnd = new Random()
//schedule("1 * * * * ? *", 'deviceHealthCheck') // test
schedule("${rnd.nextInt(59)} ${rnd.nextInt(59)} 1/3 * * ? *", 'deviceHealthCheck')
}
// called every 3 hours
def deviceHealthCheck() {
state.notPresentCounter = (state.notPresentCounter ?: 0) + 1
if (state.notPresentCounter > healthStatusCountTreshold) {
if (!(device.currentValue('healthStatus', true) in ['offline'])) {
setHealthStatusValue('offline')
log.warn "${device.displayName} is offline!"
}
}
else {
if (logEnable) log.debug "${device.displayName} deviceHealthCheck - online (notPresentCounter=${state.notPresentCounter})"
}
}
def setHealthStatusOnline() {
state.notPresentCounter = 0
if (!(device.currentValue('healthStatus', true) in ['online'])) {
setHealthStatusValue('online')
log.info "${device.displayName} is online"
}
}
def setHealthStatusValue(value) {
sendEvent(name: "healthStatus", value: value, descriptionText: "${device.displayName} healthStatus set to $value")
}
def ping() {
if (logEnable) log.debug "ping() is not implemented"
}
def logDebug(msg) {
if (settings?.logEnable) {
log.debug "${device.displayName} " + msg
}
}
def logInfo(msg) {
if (settings?.txtEnable) {
log.info "${device.displayName} " + msg
}
}
def logWarn(msg) {
if (settings?.logEnable) {
log.warn "${device.displayName} " + msg
}
}
def test(String description) {
log.warn "test: ${description}"
parse(description)
// TODO: add Centralite / Iris buttons : https://raw.githubusercontent.com/chalford-st/SmartThingsPublic/master/devicetypes/smartthings/zigbee-button.src/zigbee-button.groovy
// TODO: Check Osrma mini driver: https://raw.githubusercontent.com/chalford-st/SmartThingsPublic/master/devicetypes/chalford/osram-lightify-switch-mini.src/osram-lightify-switch-mini.groovy
// TODO: Check Aqara driver : https://raw.githubusercontent.com/jsconstantelos/SmartThings/master/devicetypes/jsconstantelos/my-aqara-double-rocker-switch-no-neutral.src/my-aqara-double-rocker-switch-no-neutral.groovy
// TODO: Check Ikea quirk : https://github.com/TheJulianJES/zha-device-handlers/blob/05c59d01683e0e929f982bf90a338c7596b3e119/zhaquirks/ikea/fourbtnremote.py
}