From eca56d96ccc2b0ff69bdcdec8b75ba23d60470ab Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sat, 27 Jan 2024 14:35:37 +0100 Subject: [PATCH 01/21] Release 7.1.008 --- .hidden/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.hidden/VERSION b/.hidden/VERSION index 4ee7d7c93..41bc14ee0 100644 --- a/.hidden/VERSION +++ b/.hidden/VERSION @@ -1 +1 @@ -{"branch": "stable7", "version": "7.1.007"} +{"branch": "stable7", "version": "7.1.008"} From 59091afe5f8de32693ba29e350dc294f0a264df7 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Fri, 2 Feb 2024 12:04:42 +0100 Subject: [PATCH 02/21] Update the message and do not mention it is an Error , but more it is a Warning that this cluster is unknown --- Modules/readClusters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/readClusters.py b/Modules/readClusters.py index c7a815acd..3f2fb2883 100644 --- a/Modules/readClusters.py +++ b/Modules/readClusters.py @@ -204,7 +204,7 @@ def ReadCluster( self, Devices, MsgType, MsgSQN, MsgSrcAddr, MsgSrcEp, MsgCluste "MsgClusterData": str(MsgClusterData), } - self.log.logging( "Cluster", "Error", "ReadCluster - Error/unknow %s/%s Cluster: %s Attribute: %s Status: %s DataType: %s DataSize: %s Data: %s" %( + self.log.logging( "Cluster", "Error", "ReadCluster - Warning - unknow %s/%s Cluster: %s Attribute: %s Status: %s DataType: %s DataSize: %s Data: %s" %( MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgAttrID, MsgAttrStatus, MsgAttType, MsgAttSize, MsgClusterData), MsgSrcAddr, _context, ) From ba4d7c56b2103d415b5cc246bcbdfc89dc4a096d Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sat, 3 Feb 2024 15:26:28 +0100 Subject: [PATCH 03/21] possibility to configure decoupled relay per line (l1, l2), https://easydomoticz.com/forum/viewtopic.php?p=119360#p119360 --- Modules/lumi.py | 18 ++++++++++++------ Modules/paramDevice.py | 6 ++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Modules/lumi.py b/Modules/lumi.py index bf549b2bf..aa514ff49 100644 --- a/Modules/lumi.py +++ b/Modules/lumi.py @@ -199,8 +199,8 @@ def xiaomi_flip_indicator_light(self, nwkid, mode): self, nwkid, ZIGATE_EP, "01", cluster_id, manuf_id, manuf_spec, Hattribute, data_type, Hdata, ackIsDisabled=is_ack_tobe_disabled(self, nwkid), ) - -def xiaomi_switch_operation_mode_opple(self, nwkid, mode): + +def xiaomi_switch_operation_mode_opple_perline(self, nwkid, ep, mode): # Mode: 0x00 Decoupled # 0x01 Control_relay if nwkid not in self.ListOfDevices: @@ -212,13 +212,19 @@ def xiaomi_switch_operation_mode_opple(self, nwkid, mode): Hattribute = "0200" data_type = "20" Hdata = "%02x" %mode + self.log.logging("Lumi", "Debug", "Write xiaomi_switch_operation_mode_opple AQARA Wireless Switch: %s/%s mode: %s" % (nwkid, ep, mode), nwkid) + write_attribute( self, nwkid, ZIGATE_EP, ep, cluster_id, manuf_id, manuf_spec, Hattribute, data_type, Hdata, ackIsDisabled=is_ack_tobe_disabled(self, nwkid), ) + +def xiaomi_switch_operation_mode_opple(self, nwkid, mode): for _ep in getListOfEpForCluster(self, nwkid, "0006"): - self.log.logging("Lumi", "Debug", "Write xiaomi_switch_operation_mode_opple AQARA Wireless Switch: %s/%s" % (nwkid, _ep), nwkid) - write_attribute( - self, nwkid, ZIGATE_EP, _ep, cluster_id, manuf_id, manuf_spec, Hattribute, data_type, Hdata, - ackIsDisabled=is_ack_tobe_disabled(self, nwkid), ) + xiaomi_switch_operation_mode_opple_perline(self, nwkid, _ep, mode) + +def xiaomi_switch_operation_mode_opple_l1(self, nwkid, mode): + xiaomi_switch_operation_mode_opple_perline(self, nwkid, "01", mode) +def xiaomi_switch_operation_mode_opple_l2(self, nwkid, mode): + xiaomi_switch_operation_mode_opple_perline(self, nwkid, "02", mode) def xiaomi_aqara_switch_mode_switch(self, nwkid, mode): # Mode: 0x04 Anti Flicker mode diff --git a/Modules/paramDevice.py b/Modules/paramDevice.py index 23344e7c8..6882ad4ec 100644 --- a/Modules/paramDevice.py +++ b/Modules/paramDevice.py @@ -40,6 +40,8 @@ xiaomi_flip_indicator_light, xiaomi_led_disabled_night, xiaomi_opple_mode, xiaomi_switch_operation_mode_opple, + xiaomi_switch_operation_mode_opple_l1, + xiaomi_switch_operation_mode_opple_l2, xiaomi_switch_power_outage_memory) from Modules.philips import (philips_led_indication, philips_set_pir_occupancySensibility, @@ -308,6 +310,8 @@ def ias_sensitivity(self, nwkid, sensitivity): "AqaraOpple_led_disabled_night": xiaomi_led_disabled_night, "AqaraOpple_flip_indicator_light": xiaomi_flip_indicator_light, "AqaraOpple_switch_operation_mode_opple": xiaomi_switch_operation_mode_opple, + "AqaraOpple_switch_operation_mode_opple_l1": xiaomi_switch_operation_mode_opple_l1, + "AqaraOpple_switch_operation_mode_opple_l2": xiaomi_switch_operation_mode_opple_l2, "AqaraOpple_aqara_switch_mode_switch": xiaomi_aqara_switch_mode_switch, "AqaraOppleMode": xiaomi_opple_mode, "TuyaPIRKeepTime": tuya_pir_keep_time_lookup, @@ -338,5 +342,3 @@ def sanity_check_of_param(self, NwkId): ts0601_actuator(self, NwkId, param, value) elif param in DEVICE_PARAMETERS: DEVICE_PARAMETERS[param](self, NwkId, value) - - From faabed82542088b6179f2c213593bca8b0db81ca Mon Sep 17 00:00:00 2001 From: Pipiche <8291674+pipiche38@users.noreply.github.com> Date: Sun, 4 Feb 2024 11:35:00 +0100 Subject: [PATCH 04/21] enable some Tuya radar parameters ( 7.1.009) (#1704) * enable some Tuya radar parameters * refactor Occupancy settings (7.1.009) (#1705) * move Sonoff functions definitions to DevicesModules/ * Fix missing 0xfcc0 polling parameter * Refactor TS0601 settings --- Classes/PluginConf.py | 222 +++++++++++++------------- DevicesModules/custom_sonoff.py | 23 ++- Modules/basicOutputs.py | 32 ---- Modules/develco.py | 15 ++ Modules/occupancy_settings.py | 268 ++++++++++++++++++++++++++++++++ Modules/paramDevice.py | 75 +++------ Modules/philips.py | 6 +- Modules/readAttributes.py | 26 +++- Modules/tuyaTS0601.py | 118 ++++++++++++-- Zigbee/zclRawCommands.py | 2 +- 10 files changed, 557 insertions(+), 230 deletions(-) create mode 100644 Modules/develco.py create mode 100644 Modules/occupancy_settings.py diff --git a/Classes/PluginConf.py b/Classes/PluginConf.py index c9bcce7e1..1bdcb9bfd 100644 --- a/Classes/PluginConf.py +++ b/Classes/PluginConf.py @@ -211,112 +211,107 @@ # Verbose "VerboseLogging": { "Order": 13, - "param": { - "enablePluginLogging": {"type": "bool","default": 1,"current": None,"restart": 1,"hidden": False,"Advanced": False,}, - "logDeviceUpdate": {"type": "bool","default": 1,"current": None,"restart": 0,"hidden": False,"Advanced": False,}, - - "trackZclClustersIn": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": False,}, - "trackZclClustersOut": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": False,}, - "trackZdpClustersIn": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": False,}, - "trackZdpClustersOut": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": False,}, - - "logThreadName": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": False,}, - "trackTransportError": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": False,}, - - "LQIthreshold": {"type": "int","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": False,}, - "MatchingNwkId": {"type": "str","default": "ffff","current": None,"restart": 0,"hidden": False,"Advanced": False,}, - "loggingBackupCount": {"type": "int","default": 7,"current": None,"restart": 1,"hidden": False,"Advanced": False,}, - "loggingMaxMegaBytes": {"type": "int","default": 0,"current": None,"restart": 1,"hidden": False,"Advanced": False,}, - "ZiGateReactTime": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "showTimeOutMsg": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - - "logFORMAT": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, - "NXPExtendedErrorCode": {"type": "bool","default": 1,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - - "coordinatorCmd": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - - "z4dCertifiedDevices": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Input": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ZclClusters": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "DeviceAnnoucement": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "BasicOutput": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Binding": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ConfigureReporting": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "WriteAttributes": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ReadAttributes": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Thermostats": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Transport": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Transport8000": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Transport8002": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Transport8011": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Transport8012": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "TransportWrter": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "TransportFrwder": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "TransportRder": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "TransportProto": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "TransportTcpip": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "TransportSerial": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "TransportZigpy": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "TransportError": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Zigpy": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ZigpyZigate": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ZigpyZNP": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ZigpydeCONZ": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ZigpyEZSP": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "TransportPluginEncoder": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Cluster": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Heartbeat": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Widget": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "WidgetCreation": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Plugin": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ListImportedModules": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Database": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Command": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Pairing": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "NetworkMap": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "NetworkEnergy": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Groups": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "OTA": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "IAS": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "DZDB": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "WebServer": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Ikea": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Legrand": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Lumi": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Heiman": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Livolo": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Tuya": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Tuya0601": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "TuyaTS011F": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Profalux": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Schneider": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "AbstractDz": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "CasaIA": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Philips": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Enki": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Gledopto": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Orvibo": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "PiZigate": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Pluzzy": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "PollControl": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "PluginTools": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Sonoff": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "PDM": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "inRawAPS": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "outRawAPS": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Timing": {"type": "bool","default": 1,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, - "Danfoss": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "zdpCommand": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "zclCommand": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "zdpDecoder": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "zclDecoder": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "zigateCommand": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ThreadForwarder": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ThreadDomoticz": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ThreadWriter": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ThreadCommunication": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "ZLinky": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": False,"Advanced": True,}, - "Garbage": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, + "param": { + "AbstractDz": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "BasicOutput": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Binding": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "CasaIA": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Cluster": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Command": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ConfigureReporting": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "DZDB": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Danfoss": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Database": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "DeviceAnnoucement": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Enki": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Garbage": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": True, "Advanced": True }, + "Gledopto": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Groups": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Heartbeat": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Heiman": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "IAS": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Ikea": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Input": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "LQIthreshold": { "type": "int", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, + "Legrand": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ListImportedModules": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Livolo": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Lumi": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "MatchingNwkId": { "type": "str", "default": "ffff", "current": None, "restart": 0, "hidden": False, "Advanced": False }, + "NXPExtendedErrorCode": { "type": "bool", "default": 1, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "NetworkEnergy": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "NetworkMap": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "OTA": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Orvibo": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "PDM": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Pairing": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Philips": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "PiZigate": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Plugin": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "PluginTools": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Pluzzy": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "PollControl": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Profalux": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ReadAttributes": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Schneider": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Sonoff": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Thermostats": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ThreadCommunication": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ThreadDomoticz": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ThreadForwarder": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ThreadWriter": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Timing": { "type": "bool", "default": 1, "current": None, "restart": 0, "hidden": True, "Advanced": True }, + "Transport": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Transport8000": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Transport8002": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Transport8011": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Transport8012": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "TransportError": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "TransportFrwder": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "TransportPluginEncoder": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "TransportProto": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "TransportRder": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "TransportSerial": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "TransportTcpip": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "TransportWrter": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "TransportZigpy": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Tuya": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Tuya0601": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "TuyaTS011F": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "WebServer": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Widget": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "WidgetCreation": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "WriteAttributes": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ZLinky": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ZclClusters": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ZiGateReactTime": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "Zigpy": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ZigpyEZSP": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ZigpyZNP": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ZigpyZigate": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "ZigpydeCONZ": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "coordinatorCmd": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "enablePluginLogging": { "type": "bool", "default": 1, "current": None, "restart": 1, "hidden": False, "Advanced": False }, + "inRawAPS": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "logDeviceUpdate": { "type": "bool", "default": 1, "current": None, "restart": 0, "hidden": False, "Advanced": False }, + "logFORMAT": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": True, "Advanced": True }, + "logThreadName": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, + "loggingBackupCount": { "type": "int", "default": 7, "current": None, "restart": 1, "hidden": False, "Advanced": False }, + "loggingMaxMegaBytes": { "type": "int", "default": 0, "current": None, "restart": 1, "hidden": False, "Advanced": False }, + "occupancySettings": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "outRawAPS": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "showTimeOutMsg": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "trackTransportError": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, + "trackZclClustersIn": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, + "trackZclClustersOut": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, + "trackZdpClustersIn": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, + "trackZdpClustersOut": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, + "z4dCertifiedDevices": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "zclCommand": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "zclDecoder": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "zdpCommand": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "zdpDecoder": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "zigateCommand": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True } }, }, # Others @@ -351,7 +346,7 @@ "Reserved": { "Order": 99, "param": { - # Just for compatibility keep it but hidden ( move to Custom device 'Param' section) + # Just for compatibility keep it but hidden ( move to Custom device "Param" section) "nPDUaPDUThreshold": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": True,"Advanced": True,"ZigpyRadio": ""}, "DropBadAnnoucement": {"type": "bool","default": 1,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, "rebindLivolo": {"type": "bool","default": 0,"current": None,"restart": 0,"hidden": True,"Advanced": False,}, @@ -406,6 +401,7 @@ "polling0b05": {"type": "int","default": 86400,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, "polling000f": {"type": "int","default": 900,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, "pollingfc00": {"type": "int","default": 300,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, + "pollingfcc0": {"type": "int","default": 300,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, "pollingfc01": {"type": "int","default": 900,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, "pollingfc11": {"type": "int","default": 900,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, "pollingfc21": {"type": "int","default": 900,"current": None,"restart": 0,"hidden": True,"Advanced": True,}, @@ -429,7 +425,7 @@ def __init__(self, zigbee_communication, VersionNewFashion, DomoticzMajor, Domot self.pluginConf = {} self.homedir = homedir self.hardwareid = hardwareid - self.pluginConf["pluginHome"] = homedir.rstrip('/').rstrip('\\') + self.pluginConf["pluginHome"] = homedir.rstrip("/").rstrip("\\") self.VersionNewFashion = VersionNewFashion self.DomoticzMajor = DomoticzMajor self.DomoticzMinor = DomoticzMinor @@ -462,8 +458,8 @@ def __init__(self, zigbee_communication, VersionNewFashion, DomoticzMajor, Domot def write_Settings(self): - # serialize json format the pluginConf ' - # Only the arameters which are different than default ' + # serialize json format the pluginConf " + # Only the arameters which are different than default " #self.pluginConf["filename"] = self.pluginConf["pluginConfig"] + "PluginConf-%02d.json" % self.hardwareid _pluginConf = Path(self.pluginConf["pluginConfig"] ) @@ -492,8 +488,8 @@ def write_Settings(self): def _load_Settings(self): - # deserialize json format of pluginConf' - # load parameters ' + # deserialize json format of pluginConf" + # load parameters " dz_timestamp = 0 if is_domoticz_db_available(self): @@ -627,7 +623,7 @@ def _path_check(self): self.write_Settings() def _param_checking(self): - # Let's check the Type + # Let"s check the Type for theme in SETTINGS: for param in SETTINGS[theme]["param"]: if self.pluginConf[param] == SETTINGS[theme]["param"][param]["default"]: diff --git a/DevicesModules/custom_sonoff.py b/DevicesModules/custom_sonoff.py index db20a8cb6..1ae4c3b63 100644 --- a/DevicesModules/custom_sonoff.py +++ b/DevicesModules/custom_sonoff.py @@ -10,23 +10,30 @@ # # SPDX-License-Identifier: GPL-3.0 license -from Modules.zigateConsts import ZIGATE_EP from Modules.basicOutputs import write_attribute +from Modules.readAttributes import ReadAttributeRequest_0406_0022 +from Modules.zigateConsts import ZIGATE_EP + +SONOFF_MAUFACTURER_NAME = "SONOFF" +SONOFF_MANUFACTURER_ID = "1286" SONOFF_CLUSTER_ID = "fc11" -SONOFF_MANUF_ID = "1286" +SONOFF_ILLUMINATION_ATTRIBUTE = "2001" + + +def is_sonoff_device(self, nwkid): + return self.ListOfDevices[nwkid]["Manufacturer"] == SONOFF_MANUFACTURER_ID or self.ListOfDevices[nwkid]["Manufacturer Name"] == SONOFF_MAUFACTURER_NAME + def sonoff_child_lock(self, nwkid, lock_mode): self.log.logging("Sonoff", "Debug", "sonoff_child_lock - Nwkid: %s Mode: %s" % (nwkid, lock_mode)) - write_attribute(self, nwkid, ZIGATE_EP, "01", SONOFF_CLUSTER_ID, SONOFF_MANUF_ID, "01", "0000", "10", "%02x" %lock_mode, ackIsDisabled=False) + write_attribute(self, nwkid, ZIGATE_EP, "01", SONOFF_CLUSTER_ID, SONOFF_MANUFACTURER_ID, "01", "0000", "10", "%02x" %lock_mode, ackIsDisabled=False) + - def sonoff_open_window_detection(self, nwkid, detection): self.log.logging("Sonoff", "Debug", "sonoff_child_lock - Nwkid: %s Mode: %s" %(nwkid, detection)) - write_attribute(self, nwkid, ZIGATE_EP, "01", SONOFF_CLUSTER_ID, SONOFF_MANUF_ID, "01", "6000", "10", "%02x" %detection, ackIsDisabled=False) - + write_attribute(self, nwkid, ZIGATE_EP, "01", SONOFF_CLUSTER_ID, SONOFF_MANUFACTURER_ID, "01", "6000", "10", "%02x" %detection, ackIsDisabled=False) SONOFF_DEVICE_PARAMETERS = { "SonOffTRVChildLock": sonoff_child_lock, - "SonOffTRVWindowDectection": sonoff_open_window_detection - + "SonOffTRVWindowDectection": sonoff_open_window_detection, } diff --git a/Modules/basicOutputs.py b/Modules/basicOutputs.py index 33799407b..684e20f12 100644 --- a/Modules/basicOutputs.py +++ b/Modules/basicOutputs.py @@ -602,38 +602,6 @@ def identifyEffect(self, nwkid, ep, effect="Blink"): return zcl_identify_trigger_effect(self, nwkid, ep, "%02x" %effect_command[effect], "%02x" % 0) - -def set_PIROccupiedToUnoccupiedDelay(self, key, delay, ListOfEp=None): - - cluster_id = "0406" - attribute = "0010" - data_type = "21" - manuf_id = "0000" - manuf_spec = "00" - if ListOfEp is None: - ListOfEp = getListOfEpForCluster(self, key, cluster_id) - for EPout in ListOfEp: - data = "%04x" % delay - self.log.logging( - "BasicOutput", "Log", "set_PIROccupiedToUnoccupiedDelay for %s/%s - delay: %s" % (key, EPout, delay), key - ) - if attribute in self.ListOfDevices[key]["Ep"][EPout][cluster_id]: - del self.ListOfDevices[key]["Ep"][EPout][cluster_id][attribute] - return write_attribute( - self, - key, - ZIGATE_EP, - EPout, - cluster_id, - manuf_id, - manuf_spec, - attribute, - data_type, - data, - ackIsDisabled=False, - ) - - def set_poweron_afteroffon(self, key, OnOffMode=0xFF): # OSRAM/LEDVANCE # 0xfc0f --> Command 0x01 diff --git a/Modules/develco.py b/Modules/develco.py new file mode 100644 index 000000000..8da276ddd --- /dev/null +++ b/Modules/develco.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Implementation of Zigbee for Domoticz plugin. +# +# This file is part of Zigbee for Domoticz plugin. https://github.com/zigbeefordomoticz/Domoticz-Zigbee +# (C) 2015-2024 +# +# Initial authors: zaraki673 & pipiche38 +# +# SPDX-License-Identifier: GPL-3.0 license + + +def is_develco_device(self, nwkid): + return self.ListOfDevices[nwkid]["Manufacturer"] == "1015" or self.ListOfDevices[nwkid]["Manufacturer Name"] == "frient A/S" diff --git a/Modules/occupancy_settings.py b/Modules/occupancy_settings.py new file mode 100644 index 000000000..6707b8651 --- /dev/null +++ b/Modules/occupancy_settings.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Implementation of Zigbee for Domoticz plugin. +# +# This file is part of Zigbee for Domoticz plugin. https://github.com/zigbeefordomoticz/Domoticz-Zigbee +# (C) 2015-2024 +# +# Initial authors: zaraki673 & pipiche38 +# +# SPDX-License-Identifier: GPL-3.0 license + +from Modules.basicOutputs import write_attribute +from Modules.develco import is_develco_device +from DevicesModules.custom_sonoff import is_sonoff_device +from Modules.philips import is_philips_device, philips_set_pir_occupancySensibility +from Modules.readAttributes import ReadAttributeRequest_0406_0010, ReadAttributeRequest_0406_0012, ReadAttributeRequest_0406_0020, ReadAttributeRequest_0406_0022 +from Modules.tools import getListOfEpForCluster +from Modules.zigateConsts import ZIGATE_EP + +OCCUPANCY_CLUSTER_ID = "0406" + +PIR_CONFIG_SET = { + "PIROccupiedToUnoccupiedDelay": ( "0010", "21"), + "PIRUnoccupiedToOccupiedDelay": ( "0011", "21"), + "PIRUnoccupiedToOccupiedThreshold": ( "0012", "20") +} + +ULTRASONIC_CONFIG_SET = { + "UltrasonicOccupiedToUnoccupiedDelay": ( "0020", "21"), + "UltrasonicUnoccupiedToOccupiedDelay": ( "0021", "21"), + "UltrasonicUnoccupiedToOccupiedThreshold": ( "0022", "20") +} + +PHYSICAL_CONFIG_SET = { + +} + +# Standard +def PIR_occupied_to_unoccupied_delay(self, nwkid, ep, value): + self.log.logging( "occupancySettings", "Debug", f"PIR_occupied_to_unoccupied_delay for {nwkid}/{ep} - delay: {value}", nwkid ) + + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + OCCUPANCY_CLUSTER_ID, + "0000", + "00", + PIR_CONFIG_SET[ "PIROccupiedToUnoccupiedDelay"][0], + PIR_CONFIG_SET[ "PIROccupiedToUnoccupiedDelay"][1], + "%04x" %value, + ackIsDisabled=False, ) + + +def current_PIR_OccupiedToUnoccupied_Delay(self, nwkid, ep): + """ look in the plugin database for the the current value of the OccupiedToUnoccupied_Delay """ + + ep_data = self.ListOfDevices.get(nwkid, {}).get("Ep", {}).get(ep, {}).get(OCCUPANCY_CLUSTER_ID, {}) + attribute_0010 = ep_data.get("0010", None) + + if attribute_0010 is not None: + return int(attribute_0010, 16) if isinstance(attribute_0010, str) else attribute_0010 + else: + return None + + +def PIR_unoccupied_to_occupied_delay(self, nwkid, ep, value): + self.log.logging( "occupancySettings", "Debug", f"PIR_unoccupied_to_occupied_delay for {nwkid}/{ep} - delay: {value}", nwkid ) + + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + OCCUPANCY_CLUSTER_ID, + "0000", + "00", + PIR_CONFIG_SET[ "PIRUnoccupiedToOccupiedDelay"][0], + PIR_CONFIG_SET[ "PIRUnoccupiedToOccupiedDelay"][1], + "%04x" %value, + ackIsDisabled=False, ) + + +def PIR_unoccupied_to_occupied_threshold(self, nwkid, ep, value): + self.log.logging( "occupancySettings", "Debug", f"PIR_unoccupied_to_occupied_threshold for {nwkid}/{ep} - thershold: {value}", nwkid ) + + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + OCCUPANCY_CLUSTER_ID, + "0000", + "00", + PIR_CONFIG_SET[ "PIRUnoccupiedToOccupiedThreshold"][0], + PIR_CONFIG_SET[ "PIRUnoccupiedToOccupiedThreshold"][1], + "%02x" %value, + ackIsDisabled=False, ) + + +def current_PIR_OccupiedToUnoccupied_Threshold(self, nwkid, ep): + """ look in the plugin database for the the current value of the OccupiedToUnoccupied_Threshold """ + + target_attribute = PIR_CONFIG_SET[ "PIRUnoccupiedToOccupiedThreshold"][0] + ep_data = self.ListOfDevices.get(nwkid, {}).get("Ep", {}).get(ep, {}).get(OCCUPANCY_CLUSTER_ID, {}) + attribute_0012 = ep_data.get(target_attribute, None) + + if attribute_0012 is not None: + return int(attribute_0012, 16) if isinstance(attribute_0012, str) else attribute_0012 + + return None + + +def Ultrasonic_occupied_to_unoccupied_delay(self, nwkid, ep, value): + self.log.logging( "occupancySettings", "Debug", f"Ultrasonic_occupied_to_unoccupied_delay for {nwkid}/{ep} - delay: {value}", nwkid ) + + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + OCCUPANCY_CLUSTER_ID, + "0000", + "00", + PIR_CONFIG_SET[ "UltrasonicOccupiedToUnoccupiedDelay"][0], + PIR_CONFIG_SET[ "UltrasonicOccupiedToUnoccupiedDelay"][1], + "%04x" %value, + ackIsDisabled=False, ) + + +def current_Ultrasonic_OccupiedToUnoccupied_Delay(self, nwkid, ep): + """ look in the plugin database for the the current value of the Ultrasonic_OccupiedToUnoccupied_Delay """ + + ep_data = self.ListOfDevices.get(nwkid, {}).get("Ep", {}).get(ep, {}).get(OCCUPANCY_CLUSTER_ID, {}) + attribute_0010 = ep_data.get("0010", None) + + if attribute_0010 is not None: + return int(attribute_0010, 16) if isinstance(attribute_0010, str) else attribute_0010 + else: + return None + + +def Ultrasonic_unoccupied_to_occupied_delay(self, nwkid, ep, value): + self.log.logging( "occupancySettings", "Debug", f"Ultrasonic_unoccupied_to_occupied_delay for {nwkid}/{ep} - delay: {value}", nwkid ) + + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + OCCUPANCY_CLUSTER_ID, + "0000", + "00", + PIR_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedDelay"][0], + PIR_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedDelay"][1], + "%04x" %value, + ackIsDisabled=False, ) + + +def Ultrasonic_unoccupied_to_occupied_threshold(self, nwkid, ep, value): + self.log.logging( "occupancySettings", "Debug", f"Ultrasonic_unoccupied_to_occupied_threshold for {nwkid}/{ep} - thershold: {value}", nwkid ) + + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + OCCUPANCY_CLUSTER_ID, + "0000", + "00", + PIR_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedThreshold"][0], + PIR_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedThreshold"][1], + "%02x" %value, + ackIsDisabled=False, ) + + +def current_Ultrasonic_unoccupied_to_occupied_threshold(self, nwkid, ep): + """ look in the plugin database for the the current value of the Ultrasonic_unoccupied_to_occupied_threshold """ + + target_attribute = PIR_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedThreshold"][0] + ep_data = self.ListOfDevices.get(nwkid, {}).get("Ep", {}).get(ep, {}).get(OCCUPANCY_CLUSTER_ID, {}) + attribute_0022 = ep_data.get( target_attribute, None) + + if attribute_0022 is not None: + return int(attribute_0022, 16) if isinstance(attribute_0022, str) else attribute_0022 + + return None + +# paramDevice helpers + +def common_PIROccupiedToUnoccupiedDelay(self, nwkid, delay): + + self.log.logging( "occupancySettings", "Debug", f"common_PIROccupiedToUnoccupiedDelay for {nwkid} - delay: {delay}", nwkid ) + + # Determine EndPoints + if is_philips_device(self, nwkid): + # work on ep 0x02 + ListOfEp = ["02",] + + elif is_develco_device(self, nwkid): + ListOfEp = ["22", "28", "29"] + + else: + ListOfEp = getListOfEpForCluster(self, nwkid, OCCUPANCY_CLUSTER_ID) + + self.log.logging( "occupancySettings", "Debug", f"common_PIROccupiedToUnoccupiedDelay for {nwkid} - delay: {delay} found Endpoint {ListOfEp}", nwkid ) + for ep in ListOfEp: + current_delay = current_PIR_OccupiedToUnoccupied_Delay(self, nwkid, ep) + if current_delay != delay: + PIR_occupied_to_unoccupied_delay(self, nwkid, ep, delay) + ReadAttributeRequest_0406_0010(self, nwkid) + + +def common_PIR_occupancySensibility(self, nwkid, sensibility): + self.log.logging( "occupancySettings", "Debug", f"common_PIR_occupancySensibility for {nwkid} - sensibility: {sensibility}", nwkid ) + + if is_philips_device(self, nwkid): + philips_set_pir_occupancySensibility(self, nwkid, sensibility) + return + + elif is_develco_device(self, nwkid): + ListOfEp = ["22", "28", "29"] + + else: + ListOfEp = getListOfEpForCluster(self, nwkid, OCCUPANCY_CLUSTER_ID) + + self.log.logging( "occupancySettings", "Debug", f"common_Ultrasonic_occupancySensibility for {nwkid} - delay: {sensibility} found Endpoint {ListOfEp}", nwkid ) + for ep in ListOfEp: + current_sensibility = current_PIR_OccupiedToUnoccupied_Threshold(self, nwkid, ep) + if current_sensibility != sensibility: + PIR_unoccupied_to_occupied_threshold(self, nwkid, ep, sensibility) + ReadAttributeRequest_0406_0012(self, nwkid) + + +def common_Ultrasnonic_OccupiedToUnoccupiedDelay(self, nwkid, delay): + self.log.logging( "occupancySettings", "Debug", f"common_Ultrasnonic_OccupiedToUnoccupiedDelay for {nwkid} - delay: {delay}", nwkid ) + + # Determine EndPoints + ListOfEp = getListOfEpForCluster(self, nwkid, OCCUPANCY_CLUSTER_ID) + + self.log.logging( "occupancySettings", "Debug", f"common_Ultrasnonic_OccupiedToUnoccupiedDelay for {nwkid} - delay: {delay} found Endpoint {ListOfEp}", nwkid ) + for ep in ListOfEp: + current_delay = current_Ultrasonic_OccupiedToUnoccupied_Delay(self, nwkid, ep) + if current_delay != delay: + Ultrasonic_occupied_to_unoccupied_delay(self, nwkid, ep, delay) + ReadAttributeRequest_0406_0020(self, nwkid) + + +def common_Ultrasonic_occupancySensibility(self, nwkid, sensibility): + self.log.logging( "occupancySettings", "Debug", f"common_Ultrasonic_occupancySensibility for {nwkid} - sensibility: {sensibility}", nwkid ) + + # Determine EndPoints + if is_sonoff_device(self, nwkid): + ListOfEp = ["01",] + + else: + ListOfEp = getListOfEpForCluster(self, nwkid, OCCUPANCY_CLUSTER_ID) + + self.log.logging( "occupancySettings", "Debug", f"common_Ultrasonic_occupancySensibility for {nwkid} - delay: {sensibility} found Endpoint {ListOfEp}", nwkid ) + for ep in ListOfEp: + current_sensibility = current_Ultrasonic_unoccupied_to_occupied_threshold(self, nwkid, ep) + if current_sensibility != sensibility: + Ultrasonic_unoccupied_to_occupied_threshold(self, nwkid, ep, sensibility) + ReadAttributeRequest_0406_0022(self, nwkid) + + \ No newline at end of file diff --git a/Modules/paramDevice.py b/Modules/paramDevice.py index 6882ad4ec..e9288c39a 100644 --- a/Modules/paramDevice.py +++ b/Modules/paramDevice.py @@ -20,7 +20,6 @@ from DevicesModules.custom_sonoff import SONOFF_DEVICE_PARAMETERS from Modules.basicOutputs import (ballast_Configuration_max_level, ballast_Configuration_min_level, - set_PIROccupiedToUnoccupiedDelay, set_poweron_afteroffon) from Modules.danfoss import (danfoss_covered, danfoss_exercise_day_of_week, danfoss_exercise_trigger_time, @@ -43,11 +42,14 @@ xiaomi_switch_operation_mode_opple_l1, xiaomi_switch_operation_mode_opple_l2, xiaomi_switch_power_outage_memory) +from Modules.occupancy_settings import ( + common_PIR_occupancySensibility, common_PIROccupiedToUnoccupiedDelay, + common_Ultrasnonic_OccupiedToUnoccupiedDelay, + common_Ultrasonic_occupancySensibility) from Modules.philips import (philips_led_indication, philips_set_pir_occupancySensibility, philips_set_poweron_after_offon_device) -from Modules.readAttributes import (ReadAttributeRequest_0006_400x, - ReadAttributeRequest_0406_0010) +from Modules.readAttributes import ReadAttributeRequest_0006_400x from Modules.schneider_wiser import (iTRV_open_window_detection, wiser_home_lockout_thermostat, wiser_lift_duration) @@ -89,8 +91,7 @@ tuya_ts011F_threshold_overTemperatureBreaker, tuya_ts011F_threshold_overVoltageBreaker, tuya_ts011F_threshold_underVoltageBreaker) -from Modules.tuyaTS0601 import (TS0601_COMMANDS, ts0601_actuator, - ts0601_extract_data_point_infos) +from Modules.tuyaTS0601 import ts0601_extract_data_point_infos, ts0601_settings def Ballast_max_level(self, nwkid, max_level): @@ -101,52 +102,6 @@ def Ballast_min_level(self, nwkid, min_level): ballast_Configuration_min_level(self, nwkid, min_level) -def param_Occupancy_settings_PIROccupiedToUnoccupiedDelay(self, nwkid, delay): - # Based on Philips HUE - # 0x00 default - # The PIROccupiedToUnoccupiedDelay attribute is 16 bits in length and - # specifies the time delay, in seconds,before the PIR sensor changes to - # its unoccupied state after the last detection of movement in the sensed area. - - if self.ListOfDevices[nwkid]["Manufacturer"] == "100b" or self.ListOfDevices[nwkid]["Manufacturer Name"] == "Philips": # Philips - if "02" not in self.ListOfDevices[nwkid]["Ep"]: - return - if "0406" not in self.ListOfDevices[nwkid]["Ep"]["02"]: - return - if "0010" not in self.ListOfDevices[nwkid]["Ep"]["02"]["0406"]: - set_PIROccupiedToUnoccupiedDelay(self, nwkid, delay) - ReadAttributeRequest_0406_0010(self, nwkid) - return - - current_delay = int(self.ListOfDevices[nwkid]["Ep"]["02"]["0406"]["0010"], 16) if isinstance( self.ListOfDevices[nwkid]["Ep"]["02"]["0406"]["0010"], str) else self.ListOfDevices[nwkid]["Ep"]["02"]["0406"]["0010"] - if current_delay != delay: - set_PIROccupiedToUnoccupiedDelay(self, nwkid, delay) - ReadAttributeRequest_0406_0010(self, nwkid) - - elif self.ListOfDevices[nwkid]["Manufacturer"] == "1015" or self.ListOfDevices[nwkid]["Manufacturer Name"] == "frient A/S": # Frientd - # delay = 10 * delay # Tenth of seconds - for ep in ["22", "28", "29"]: - if ep == "28" and "PIROccupiedToUnoccupiedDelay_28" in self.ListOfDevices[nwkid]["Param"]: - delay = int(self.ListOfDevices[nwkid]["Param"]["PIROccupiedToUnoccupiedDelay_28"]) - elif ep == "29" and "PIROccupiedToUnoccupiedDelay_29" in self.ListOfDevices[nwkid]["Param"]: - delay = int(self.ListOfDevices[nwkid]["Param"]["PIROccupiedToUnoccupiedDelay_29"]) - if ep not in self.ListOfDevices[nwkid]["Ep"]: - continue - if "0406" not in self.ListOfDevices[nwkid]["Ep"][ep]: - continue - if "0010" not in self.ListOfDevices[nwkid]["Ep"][ep]["0406"]: - set_PIROccupiedToUnoccupiedDelay(self, nwkid, delay, ListOfEp=[ep]) - ReadAttributeRequest_0406_0010(self, nwkid) - return - current_delay = int(self.ListOfDevices[nwkid]["Ep"][ep]["0406"]["0010"], 16) if isinstance( self.ListOfDevices[nwkid]["Ep"][ep]["0406"]["0010"], str) else self.ListOfDevices[nwkid]["Ep"][ep]["0406"]["0010"] - if current_delay != delay: - set_PIROccupiedToUnoccupiedDelay(self, nwkid, delay, ListOfEp=[ep]) - ReadAttributeRequest_0406_0010(self, nwkid) - - else: - self.log.logging("Heartbeat", "Log", "=====> Unknown Manufacturer/Name") - - def param_PowerOnAfterOffOn(self, nwkid, mode): # 0 - stay Off after a Off/On # 1 - stay On after a Off/On @@ -245,8 +200,11 @@ def ias_sensitivity(self, nwkid, sensitivity): DEVICE_PARAMETERS = { "HueLedIndication": philips_led_indication, "PowerOnAfterOffOn": param_PowerOnAfterOffOn, - "PIROccupiedToUnoccupiedDelay": param_Occupancy_settings_PIROccupiedToUnoccupiedDelay, - "occupancySensibility": philips_set_pir_occupancySensibility, + "PIROccupiedToUnoccupiedDelay": common_PIROccupiedToUnoccupiedDelay, + "PIRoccupancySensibility": common_PIR_occupancySensibility, + "occupancySensibility": common_PIR_occupancySensibility, + "UltrasonicOccupiedToUnoccupiedDelay": common_Ultrasnonic_OccupiedToUnoccupiedDelay, + "UltrasonicOccupancySensibility": common_Ultrasonic_occupancySensibility, "netatmoLedIfOn": legrand_enable_Led_IfOn_by_nwkid, "netatmoLedInDark": legrand_enable_Led_InDark_by_nwkid, "netatmoLedShutter": legrand_enable_Led_Shutter_by_nwkid, @@ -332,13 +290,18 @@ def ias_sensitivity(self, nwkid, sensitivity): def sanity_check_of_param(self, NwkId): + self.log.logging("Heartbeat", "Debug", f"sanity_check_of_param {NwkId}") DEVICE_PARAMETERS.update(SONOFF_DEVICE_PARAMETERS) param_data = self.ListOfDevices.get(NwkId, {}).get("Param", {}) model_name = self.ListOfDevices.get(NwkId, {}).get("Model", "") for param, value in param_data.items(): - if ts0601_extract_data_point_infos(self, model_name) and param in TS0601_COMMANDS: - ts0601_actuator(self, NwkId, param, value) + self.log.logging("Heartbeat", "Debug", f"sanity_check_of_param {param}, {value}") + + dps_mapping = ts0601_extract_data_point_infos( self, model_name) + if dps_mapping: + ts0601_settings( self, NwkId, dps_mapping, param, value) + elif param in DEVICE_PARAMETERS: - DEVICE_PARAMETERS[param](self, NwkId, value) + DEVICE_PARAMETERS[param](self, NwkId, value) \ No newline at end of file diff --git a/Modules/philips.py b/Modules/philips.py index 1272713c6..e459be222 100644 --- a/Modules/philips.py +++ b/Modules/philips.py @@ -27,6 +27,9 @@ PHILIPS_POWERON_MODE = {0x00: "Off", 0x01: "On", 0xFF: "Previous state"} # Off # On # Previous state +def is_philips_device(self, nwkid): + return self.ListOfDevices[nwkid]["Manufacturer"] == "100b" or self.ListOfDevices[nwkid]["Manufacturer Name"] == "Philips" + def pollingPhilips(self, key): """ @@ -161,6 +164,7 @@ def philips_set_poweron_after_offon_device(self, mode, nwkid): set_poweron_afteroffon(self, nwkid, OnOffMode=mode) ReadAttributeRequest_0006_400x(self, nwkid) + def zigpy_philips_dimmer_switch(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, dstNWKID, dstEP, MsgPayload): # 1d/0b10/2400/0300/00/30|01|21|1800 (long press) # xxxx -> duration @@ -172,6 +176,7 @@ def zigpy_philips_dimmer_switch(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterI MsgClusterData = MsgPayload[14 + 2:] philips_dimmer_switch(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgAttrID, MsgClusterData) + def philips_dimmer_switch(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgAttrID, MsgClusterData): self.log.logging( "Philips", "Debug", "philips_dimmer_switch %s - %s/%s - MsgAttrID = %s MsgClusterData: %s" % ( @@ -249,7 +254,6 @@ def philips_dimmer_switch(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, Msg MsgClusterId, MsgSrcAddr, MsgSrcEp, MsgAttrID, MsgClusterData), MsgSrcAddr, ) - def _update_domoticz_widget( self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, prev_onoffvalue, onoffValue, prev_lvlValue, lvlValue, duration ): # Update Domo sonoffValue = "%02x" % onoffValue diff --git a/Modules/readAttributes.py b/Modules/readAttributes.py index cb17d8dd7..f923de269 100644 --- a/Modules/readAttributes.py +++ b/Modules/readAttributes.py @@ -1189,10 +1189,33 @@ def ReadAttributeRequest_0406_0010(self, key): for EPout in ListOfEp: ReadAttributeReq(self, key, ZIGATE_EP, EPout, "0406", listAttributes, ackIsDisabled=is_ack_tobe_disabled(self, key)) +def ReadAttributeRequest_0406_0012(self, key): + + self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0406/0012- Key: %s " % key, nwkid=key) + ListOfEp = getListOfEpForCluster(self, key, "0406") + listAttributes = [0x0012] + for EPout in ListOfEp: + ReadAttributeReq(self, key, ZIGATE_EP, EPout, "0406", listAttributes, ackIsDisabled=is_ack_tobe_disabled(self, key)) + +def ReadAttributeRequest_0406_0020(self, key): + + self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0406/0020- Key: %s " % key, nwkid=key) + ListOfEp = getListOfEpForCluster(self, key, "0406") + listAttributes = [0x0020] + for EPout in ListOfEp: + ReadAttributeReq(self, key, ZIGATE_EP, EPout, "0406", listAttributes, ackIsDisabled=is_ack_tobe_disabled(self, key)) + +def ReadAttributeRequest_0406_0022(self, key): + + self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0406/0022- Key: %s " % key, nwkid=key) + ListOfEp = getListOfEpForCluster(self, key, "0406") + listAttributes = [0x0022] + for EPout in ListOfEp: + ReadAttributeReq(self, key, ZIGATE_EP, EPout, "0406", listAttributes, ackIsDisabled=is_ack_tobe_disabled(self, key)) def ReadAttributeRequest_0406_philips_0030(self, key): manufacturer = "100b" - self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0406/0010- Key: %s " % key, nwkid=key) + self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0406/0030- Key: %s " % key, nwkid=key) ListOfEp = getListOfEpForCluster(self, key, "0406") listAttrSpecific = [0x0030] for EPout in ListOfEp: @@ -1208,7 +1231,6 @@ def ReadAttributeRequest_0406_philips_0030(self, key): ackIsDisabled=is_ack_tobe_disabled(self, key), ) - def ReadAttributeRequest_0500(self, key): self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0500 - Key: %s " % key, nwkid=key) diff --git a/Modules/tuyaTS0601.py b/Modules/tuyaTS0601.py index 87e82200d..d4b368d89 100644 --- a/Modules/tuyaTS0601.py +++ b/Modules/tuyaTS0601.py @@ -31,7 +31,7 @@ def ts0601_response(self, Devices, model_name, NwkId, Ep, dp, datatype, data): str_dp = "%02x" %dp if str_dp not in dps_mapping: - self.log.logging("Tuya0601", "Log", "ts0601_response - unknow dp %s %s %s %s %s" % ( + self.log.logging("Tuya0601", "Log", "ts0601_response - warning/unknow dp %s %s %s %s %s" % ( NwkId, str_dp, datatype, data, str(dps_mapping)), NwkId) store_tuya_attribute(self, NwkId, "UnknowDp_0x%02x_Dt_0x%02x" % (dp, datatype) , data) return False @@ -58,9 +58,12 @@ def process_dp_item( self, Devices, model_name, NwkId, Ep, dp, datatype, data, d value = evaluate_expression_with_data(self, dps_mapping_item[ "EvalExp"], value) self.log.logging("Tuya0601", "Debug", " - after evaluate_expression_with_data() value: %s" % (value), NwkId) - if "store_tuya_attribute" in dps_mapping_item: + if "store_tuya_value" in dps_mapping_item: + store_tuya_attribute(self, NwkId, dps_mapping_item["store_tuya_value"], value) + + elif "store_tuya_attribute" in dps_mapping_item: store_tuya_attribute(self, NwkId, dps_mapping_item["store_tuya_attribute"], data) - + return sensor_type( self, Devices, NwkId, Ep, value, dp, datatype, data, dps_mapping_item ) @@ -123,6 +126,11 @@ def ts0601_actuator( self, NwkId, command, value=None): command, dp, value)) if command in TS0601_COMMANDS: + if len(TS0601_COMMANDS[ command ]) == 2: + dt = TS0601_COMMANDS[ command ][1] + ts0601_tuya_action(self, NwkId, "01", command, dp, dt, value) + return + func = TS0601_COMMANDS[ command ] else: func = DP_ACTION_FUNCTION[ command ] @@ -330,7 +338,7 @@ def ts0601_summation_energy(self, Devices, nwkid, ep, value): store_tuya_attribute(self, nwkid, "Energy_%s" %ep, value) def ts0601_summation_energy_raw(self, Devices, nwkid, ep, value): - self.log.logging( "Tuya0601", "Log", "ts0601_summation_energy - Current Summation %s %s %s" % (nwkid, ep, value), nwkid, ) + self.log.logging( "Tuya0601", "Debug", "ts0601_summation_energy - Current Summation %s %s %s" % (nwkid, ep, value), nwkid, ) MajDomoDevice(self, Devices, nwkid, ep, "0702", value, Attribute_="0000") checkAndStoreAttributeValue(self, nwkid, ep, "0702", "0000", value) # Store int store_tuya_attribute(self, nwkid, "ConsumedEnergy_%s" %ep, value) @@ -471,7 +479,7 @@ def ts0601_smoke_concentration(self, Devices, nwkid, ep, value): MajDomoDevice(self, Devices, nwkid, ep, "042a", value) def ts0601_water_consumption(self, Devices, nwkid, ep, value): - self.log.logging("Tuya0601", "Log", "ts0601_water_consumption - Nwkid: %s/%s WaterConsumtpion: %s" % (nwkid, ep, value)) + self.log.logging("Tuya0601", "Debug", "ts0601_water_consumption - Nwkid: %s/%s WaterConsumtpion: %s" % (nwkid, ep, value)) store_tuya_attribute(self, nwkid, "WaterConsumtpion", value) # The counter will be treated with the divider which is defined in the parameters in the application settings # (menu setup-settings, tab counters). @@ -482,7 +490,7 @@ def ts0601_water_consumption(self, Devices, nwkid, ep, value): MajDomoDevice(self, Devices, nwkid, ep, "WaterCounter", value) def ts0601_sensor_irrigation_mode(self, Devices, nwkid, ep, value): - self.log.logging("Tuya0601", "Log", "ts0601_sensor_irrigation_mode - Nwkid: %s/%s Mode: %s" % (nwkid, ep, value)) + self.log.logging("Tuya0601", "Debug", "ts0601_sensor_irrigation_mode - Nwkid: %s/%s Mode: %s" % (nwkid, ep, value)) store_tuya_attribute(self, nwkid, "Mode", value) MajDomoDevice(self, Devices, nwkid, ep, "0008", value) @@ -533,8 +541,42 @@ def ts0601_tuya_cmd(self, NwkId, Ep, action, data): cluster_frame = "11" sqn = get_and_inc_ZCL_SQN(self, NwkId) + self.log.logging("Tuya0601", "Debug", "ts0601_tuya_cmd - %s %s sqn: %s" % (NwkId, Ep, sqn)) tuya_cmd(self, NwkId, Ep, cluster_frame, sqn, "00", action, data) - + + +def ts0601_tuya_action(self, NwkId, Ep, action, dp, dt, value): + """ + Perform a Tuya action in a most generic way + + Args: + NwkId: The network ID. (str) + Ep: The endpoint. (str) + action: The action to perform. (str for logging purposes) + dp: The data point. ( int datapoint code) + dt: The data type. ( str "01", "02", "04") + value: The value to set. ( int corresponding to the number to be sent) + + Returns: + None + + """ + self.log.logging("Tuya0601", "Debug", "ts0601_tuya_action - %s %s/%s dp: %s dt: %s value: %s" % (action, NwkId, Ep, dp, dt, value)) + if value is None: + return + + if dt in ["01", "04"]: + data_format = "%02x" + + elif dt == "02": + data_format = "%08x" + + action = f"{dp:02x}{dt}" + data = data_format % value + self.log.logging("Tuya0601", "Debug", "ts0601_tuya_action - %s %s/%s action: %s data: %s" %(action, NwkId, Ep, action, data)) + ts0601_tuya_cmd(self, NwkId, Ep, action, data) + + def ts0601_action_setpoint(self, NwkId, Ep, dp, value): # The Setpoint is coming in centi-degre (default) if value is None: @@ -545,7 +587,28 @@ def ts0601_action_setpoint(self, NwkId, Ep, dp, value): action = "%02x02" % dp data = "%08x" % value ts0601_tuya_cmd(self, NwkId, Ep, action, data) - + + +def ts0601_settings( self, NwkId, dps_mapping, param, value): + """ Handle in a more generic way TS0601 settings, by extracting Data Type from the config """ + + self.log.logging("Tuya0601", "Debug", f"ts0601_settings {NwkId}") + + for key, dps_value in dps_mapping.items(): + self.log.logging("Tuya0601", "Debug", f"ts0601_settings {key}:{dps_value}") + if "action_type" in dps_value and dps_value["action_type"] == param: + dt = dps_value[ "data_type"] if "data_type" in dps_value else None + if dt: + dp = int( key, 16) + self.log.logging("Tuya0601", "Debug", f"ts0601_settings {param} {dp} {dt} {value}") + ts0601_tuya_action(self, NwkId, "01", param, dp, dt, value) + return + + if param in TS0601_COMMANDS: + self.log.logging("Tuya0601", "Debug", f"sanity_check_of_param {param} {value}") + ts0601_actuator(self, NwkId, param, value) + + def ts0601_action_calibration(self, NwkId, Ep, dp, value=None): self.log.logging("Tuya0601", "Debug", "ts0601_action_calibration - %s Calibration: %s" % (NwkId, value)) @@ -568,6 +631,7 @@ def ts0601_action_calibration(self, NwkId, Ep, dp, value=None): data = "%08x" % value ts0601_tuya_cmd(self, NwkId, Ep, action, data) + def ts0601_window_detection_mode( self, NwkId, Ep, dp, value=None): if value is None: return @@ -586,6 +650,7 @@ def ts0601_child_lock_mode( self, NwkId, Ep, dp, value=None): data = "%02x" % value ts0601_tuya_cmd(self, NwkId, Ep, action, data) + def ts0601_action_trv7_system_mode(self, NwkId, Ep, dp, value=None): if value is None: return @@ -605,7 +670,8 @@ def ts0601_action_trv7_system_mode(self, NwkId, Ep, dp, value=None): action = "%02x04" % dp # Mode data = "%02x" % (device_value) ts0601_tuya_cmd(self, NwkId, Ep, action, data) - + + def ts0601_action_trv6_system_mode(self, NwkId, Ep, dp, value=None): if value is None: return @@ -626,6 +692,7 @@ def ts0601_action_trv6_system_mode(self, NwkId, Ep, dp, value=None): data = "%02x" % (device_value) ts0601_tuya_cmd(self, NwkId, Ep, action, data) + def ts0601_action_siren_switch(self, NwkId, Ep, dp, value=None): if value is None: return @@ -638,6 +705,7 @@ def ts0601_action_siren_switch(self, NwkId, Ep, dp, value=None): data = "%02x" % (device_value) ts0601_tuya_cmd(self, NwkId, Ep, action, data) + def ts0601_tamper_siren_switch(self, NwkId, Ep, dp, value=None): if value is None: return @@ -663,6 +731,7 @@ def ts0601_action_switch(self, NwkId, Ep, dp, value=None): data = "%02x" % (device_value) ts0601_tuya_cmd(self, NwkId, Ep, action, data) + def ts0601_irrigation_mode(self, NwkId, Ep, dp, value=None): # 0 Capacity ( Litter ) # 1 Duration ( Seconds) @@ -670,7 +739,7 @@ def ts0601_irrigation_mode(self, NwkId, Ep, dp, value=None): if value is None: return - self.log.logging("Tuya0601", "Debug", "ts0601_action_switch - %s Switch Action: dp:%s value: %s" % ( + self.log.logging("Tuya0601", "Debug", "ts0601_irrigation_mode - %s Switch Action: dp:%s value: %s" % ( NwkId, dp, value)) device_value = value @@ -686,12 +755,13 @@ def check_irrigation_valve_target_value(value, mode): return SAFETY_MIN_SECS else: return value - + + def ts0601_irrigation_valve_target( self, NwkId, Ep, dp, value=None): if value is None: return - self.log.logging("Tuya0601", "Log", "ts0601_irrigation_valve_target - %s Switch Action: dp:%s value: %s" % ( + self.log.logging("Tuya0601", "Debug", "ts0601_irrigation_valve_target - %s Switch Action: dp:%s value: %s" % ( NwkId, dp, value)) mode = get_tuya_attribute(self, NwkId, 'Mode') @@ -702,11 +772,12 @@ def ts0601_irrigation_valve_target( self, NwkId, Ep, dp, value=None): action = "%02x02" % dp # Irrigation Target (Time or Litters) data = "%08x" % (device_value) ts0601_tuya_cmd(self, NwkId, Ep, action, data) - + + def ts0601_solar_siren_alarm_melody( self, NwkId, Ep, dp, melody=None): if melody is None: return - self.log.logging("Tuya0601", "Log", "ts0601_solar_siren_alarm_melody - %s Switch Action: dp:%s value: %s" % ( + self.log.logging("Tuya0601", "Debug", "ts0601_solar_siren_alarm_melody - %s Switch Action: dp:%s value: %s" % ( NwkId, dp, melody)) if melody is None: return @@ -714,10 +785,11 @@ def ts0601_solar_siren_alarm_melody( self, NwkId, Ep, dp, melody=None): data = "%02x" % (melody) ts0601_tuya_cmd(self, NwkId, Ep, action, data) + def ts0601_solar_siren_alarm_mode( self, NwkId, Ep, dp, mode=None): if mode is None: return - self.log.logging("Tuya0601", "Log", "ts0601_solar_siren_alarm_mode - %s Switch Action: dp:%s value: %s" % ( + self.log.logging("Tuya0601", "Debug", "ts0601_solar_siren_alarm_mode - %s Switch Action: dp:%s value: %s" % ( NwkId, dp, mode)) if mode is None: return @@ -725,16 +797,28 @@ def ts0601_solar_siren_alarm_mode( self, NwkId, Ep, dp, mode=None): data = "%02x" % (mode) ts0601_tuya_cmd(self, NwkId, Ep, action, data) + def ts0601_solar_siren_alarm_duration( self, NwkId, Ep, dp, duration=None): if duration is None: return - self.log.logging("Tuya0601", "Log", "ts0601_solar_siren_alarm_duration - %s Switch Action: dp:%s value: %s" % ( + self.log.logging("Tuya0601", "Debug", "ts0601_solar_siren_alarm_duration - %s Switch Action: dp:%s value: %s" % ( NwkId, dp, duration)) action = "%02x02" % dp # I data = "%08x" % (duration) ts0601_tuya_cmd(self, NwkId, Ep, action, data) + TS0601_COMMANDS = { + "TuyaPresenceSensitivity": ( None, "04"), + "TuyaRadarSensitivity": (None, "04"), + "TuyaRadarMaxRange": ( None, "02" ), + "LargeMotionDetectionDistance": (None, "02"), + "MediumMotionDetectionDistance": (None, "02"), + "SmallDetectionDistance": (None, "02"), + "LargeMotionDetectionSensitivity": (None, "04"), + "MediumMotionDetectionSensitivity": (None, "04"), + "SmallDetectionSensitivity": ( None, "04"), + "TuyaFadingTime": ( None, "02"), "TRV7WindowDetection": ts0601_window_detection_mode, "TRV7ChildLock": ts0601_child_lock_mode, "TuyaIrrigationTarget": ts0601_irrigation_valve_target, @@ -751,5 +835,5 @@ def ts0601_solar_siren_alarm_duration( self, NwkId, Ep, dp, duration=None): "TRV6SystemMode": ts0601_action_trv6_system_mode, "TRV7SystemMode": ts0601_action_trv7_system_mode, "TuyaAlarmSwitch": ts0601_action_siren_switch, - "TuyaTamperSwitch": ts0601_tamper_siren_switch + "TuyaTamperSwitch": ts0601_tamper_siren_switch, } diff --git a/Zigbee/zclRawCommands.py b/Zigbee/zclRawCommands.py index 93d52486d..c69265c93 100644 --- a/Zigbee/zclRawCommands.py +++ b/Zigbee/zclRawCommands.py @@ -32,7 +32,7 @@ def zcl_raw_reset_device(self, nwkid, epin, epout): The sequence number used in the command. """ - self.log.logging("zclCommand", "Debug", "zcl_raw_default_response %s" % nwkid) + self.log.logging("zclCommand", "Debug", "zcl_raw_reset_device %s" % nwkid) frame_control_field = "%02x" %0b0001_0001 cmd = "00" cluster = "0000" From 331eb209d3f5c1b88496a80549ef6c649b4e000c Mon Sep 17 00:00:00 2001 From: Pipiche <8291674+pipiche38@users.noreply.github.com> Date: Sun, 4 Feb 2024 12:10:53 +0100 Subject: [PATCH 05/21] dedicate a module for OnOff settings (#1707) * dedicate a module for OnOff settings --- Classes/PluginConf.py | 1 + Modules/enki.py | 3 + Modules/occupancy_settings.py | 19 ++++- Modules/onoff_settings.py | 155 ++++++++++++++++++++++++++++++++++ Modules/paramDevice.py | 16 ++-- Modules/tuya.py | 7 +- 6 files changed, 185 insertions(+), 16 deletions(-) create mode 100644 Modules/onoff_settings.py diff --git a/Classes/PluginConf.py b/Classes/PluginConf.py index 1bdcb9bfd..6ed168676 100644 --- a/Classes/PluginConf.py +++ b/Classes/PluginConf.py @@ -299,6 +299,7 @@ "loggingBackupCount": { "type": "int", "default": 7, "current": None, "restart": 1, "hidden": False, "Advanced": False }, "loggingMaxMegaBytes": { "type": "int", "default": 0, "current": None, "restart": 1, "hidden": False, "Advanced": False }, "occupancySettings": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "onoffSettings": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, "outRawAPS": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, "showTimeOutMsg": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, "trackTransportError": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, diff --git a/Modules/enki.py b/Modules/enki.py index 7fd601cdd..780c5e6ee 100644 --- a/Modules/enki.py +++ b/Modules/enki.py @@ -8,6 +8,9 @@ ENKI_POWERON_MODE = {0x00: "Off", 0x01: "On", 0xFF: "Previous state"} # Off # On # Previous state +def is_enky_device(self, nwkid): + return self.ListOfDevices[nwkid]["Manufacturer"] == "1277" + def enki_set_poweron_after_offon(self, mode): # call from WebServer diff --git a/Modules/occupancy_settings.py b/Modules/occupancy_settings.py index 6707b8651..7921cc25c 100644 --- a/Modules/occupancy_settings.py +++ b/Modules/occupancy_settings.py @@ -10,11 +10,15 @@ # # SPDX-License-Identifier: GPL-3.0 license +from DevicesModules.custom_sonoff import is_sonoff_device from Modules.basicOutputs import write_attribute from Modules.develco import is_develco_device -from DevicesModules.custom_sonoff import is_sonoff_device -from Modules.philips import is_philips_device, philips_set_pir_occupancySensibility -from Modules.readAttributes import ReadAttributeRequest_0406_0010, ReadAttributeRequest_0406_0012, ReadAttributeRequest_0406_0020, ReadAttributeRequest_0406_0022 +from Modules.philips import (is_philips_device, + philips_set_pir_occupancySensibility) +from Modules.readAttributes import (ReadAttributeRequest_0406_0010, + ReadAttributeRequest_0406_0012, + ReadAttributeRequest_0406_0020, + ReadAttributeRequest_0406_0022) from Modules.tools import getListOfEpForCluster from Modules.zigateConsts import ZIGATE_EP @@ -265,4 +269,11 @@ def common_Ultrasonic_occupancySensibility(self, nwkid, sensibility): Ultrasonic_unoccupied_to_occupied_threshold(self, nwkid, ep, sensibility) ReadAttributeRequest_0406_0022(self, nwkid) - \ No newline at end of file + +OCCUPANCY_DEVICE_PARAMETERS = { + "PIROccupiedToUnoccupiedDelay": common_PIROccupiedToUnoccupiedDelay, + "PIRoccupancySensibility": common_PIR_occupancySensibility, + "occupancySensibility": common_PIR_occupancySensibility, + "UltrasonicOccupiedToUnoccupiedDelay": common_Ultrasnonic_OccupiedToUnoccupiedDelay, + "UltrasonicOccupancySensibility": common_Ultrasonic_occupancySensibility, +} \ No newline at end of file diff --git a/Modules/onoff_settings.py b/Modules/onoff_settings.py new file mode 100644 index 000000000..e10162139 --- /dev/null +++ b/Modules/onoff_settings.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Implementation of Zigbee for Domoticz plugin. +# +# This file is part of Zigbee for Domoticz plugin. https://github.com/zigbeefordomoticz/Domoticz-Zigbee +# (C) 2015-2024 +# +# Initial authors: zaraki673 & pipiche38 +# +# SPDX-License-Identifier: GPL-3.0 license + +from Modules.basicOutputs import write_attribute +from Modules.enki import enki_set_poweron_after_offon_device, is_enky_device +from Modules.philips import is_philips_device +from Modules.readAttributes import ReadAttributeRequest_0006_400x +from Modules.tools import get_deviceconf_parameter_value, getListOfEpForCluster +from Modules.tuya import (get_tuya_attribute, is_tuya_switch_relay, + tuya_switch_relay_status) +from Modules.zigateConsts import ZIGATE_EP + +ONOFF_CLUSTER_ID = "0006" + +ONOFF_CONFIG_SET = { + "OnTime": ( "4001", "21"), + "OffWaitTime": ( "4002", "21"), + "StartupOnOff": ( "4003", "30") +} + + +def onoff_on_time(self, nwkid, ep, value): + self.log.logging( "onoffSettings", "Debug", f"onoff_on_time for {nwkid}/{ep} - value: {value}", nwkid ) + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + ONOFF_CLUSTER_ID, + "0000", + "00", + ONOFF_CONFIG_SET[ "OnTime"][0], + ONOFF_CONFIG_SET[ "OnTime"][1], + "%04x" %value, + ackIsDisabled=False, ) + + +def onoff_off_wait_time(self, nwkid, ep, value): + + self.log.logging( "onoffSettings", "Debug", f"onoff_off_wait_time for {nwkid}/{ep} - value: {value}", nwkid ) + + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + ONOFF_CLUSTER_ID, + "0000", + "00", + ONOFF_CONFIG_SET[ "OffWaitTime"][0], + ONOFF_CONFIG_SET[ "OffWaitTime"][1], + "%04x" %value, + ackIsDisabled=False, ) + + +def onoff_startup_onoff_mode(self, nwkid, ep, value): + + self.log.logging( "onoffSettings", "Debug", f"onoff_startup_onoff_mode for {nwkid}/{ep} - value: {value}", nwkid ) + + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + ONOFF_CLUSTER_ID, + "0000", + "00", + ONOFF_CONFIG_SET[ "StartupOnOff"][0], + ONOFF_CONFIG_SET[ "StartupOnOff"][1], + "%02x" %value, + ackIsDisabled=False, ) + + +def current_onoff_startup_mode(self, nwkid, ep): + + model = self.ListOfDevices[nwkid].get("Model", "") + device = self.ListOfDevices.get(nwkid, {}) + ep_data = device.get("Ep", {}).get(ep, {}) + + if ep not in ep_data: + self.log.logging("onoffSettings", "Debug", "current_onoff_startup_mode - No ep: %s" % ep, nwkid) + return None + + onoff_cluster_data = ep_data.get(ONOFF_CLUSTER_ID, {}) + + if get_deviceconf_parameter_value(self, model, "PowerOnOffStateAttribute8002", return_default=False): + attribute_lookup = "8002" + else: + attribute_lookup = ONOFF_CONFIG_SET["StartupOnOff"][0] + + if attribute_lookup not in onoff_cluster_data: + self.log.logging("onoffSettings", "Debug", "current_onoff_startup_mode - No Attribute: %s" % attribute_lookup, nwkid) + return None + + return onoff_cluster_data[attribute_lookup] + + +def common_onoff_on_time(self, nwkid, mode): + self.log.logging( "onoffSettings", "Debug", f"common_onoff_on_time for {nwkid} - mode: {mode}", nwkid ) + ListOfEp = getListOfEpForCluster(self, nwkid, ONOFF_CLUSTER_ID) + for ep in ListOfEp: + onoff_on_time(self, nwkid, ep, mode) + +def common_onoff_off_wait_time(self, nwkid, mode): + self.log.logging( "onoffSettings", "Debug", f"common_onoff_off_wait_time for {nwkid} - mode: {mode}", nwkid ) + ListOfEp = getListOfEpForCluster(self, nwkid, ONOFF_CLUSTER_ID) + for ep in ListOfEp: + onoff_off_wait_time(self, nwkid, ep, mode) + + +def common_onoff_startup_onoff_mode(self, nwkid, mode): + + self.log.logging( "onoffSettings", "Debug", f"common_onoff_startup_onoff_mode for {nwkid} - mode: {mode}", nwkid ) + + if int(mode) not in (0, 1, 2, 255): + self.log.logging( "onoffSettings", "Error", f"common_onoff_startup_onoff_mode for {nwkid} - uncorrect mode: {mode}", nwkid ) + return + + # Determine EndPoints + if is_philips_device(self, nwkid): + ListOfEp = ["0b",] + + elif is_enky_device(self, nwkid): # Enki Leroy Merlin + enki_set_poweron_after_offon_device(self, mode, nwkid) + return + + elif is_tuya_switch_relay(self, nwkid): + if get_tuya_attribute(self, nwkid, "RelayStatus") != mode: + tuya_switch_relay_status(self, nwkid, mode) + return + + else: + ListOfEp = getListOfEpForCluster(self, nwkid, ONOFF_CLUSTER_ID) + + for ep in ListOfEp: + current_mode = current_onoff_startup_mode(self, nwkid, ep) + if current_mode != mode: + onoff_startup_onoff_mode(self, nwkid, ep, mode) + ReadAttributeRequest_0006_400x(self, nwkid) + + +ONOFF_DEVICE_PARAMETERS = { + "PowerOnAfterOffOn": common_onoff_startup_onoff_mode, + "OnOffOnTimeDelay": common_onoff_off_wait_time, + "OnOffOffWaitTime": common_onoff_off_wait_time +} \ No newline at end of file diff --git a/Modules/paramDevice.py b/Modules/paramDevice.py index e9288c39a..4acd1196a 100644 --- a/Modules/paramDevice.py +++ b/Modules/paramDevice.py @@ -42,12 +42,9 @@ xiaomi_switch_operation_mode_opple_l1, xiaomi_switch_operation_mode_opple_l2, xiaomi_switch_power_outage_memory) -from Modules.occupancy_settings import ( - common_PIR_occupancySensibility, common_PIROccupiedToUnoccupiedDelay, - common_Ultrasnonic_OccupiedToUnoccupiedDelay, - common_Ultrasonic_occupancySensibility) +from Modules.occupancy_settings import OCCUPANCY_DEVICE_PARAMETERS +from Modules.onoff_settings import ONOFF_DEVICE_PARAMETERS from Modules.philips import (philips_led_indication, - philips_set_pir_occupancySensibility, philips_set_poweron_after_offon_device) from Modules.readAttributes import ReadAttributeRequest_0006_400x from Modules.schneider_wiser import (iTRV_open_window_detection, @@ -199,12 +196,6 @@ def ias_sensitivity(self, nwkid, sensitivity): DEVICE_PARAMETERS = { "HueLedIndication": philips_led_indication, - "PowerOnAfterOffOn": param_PowerOnAfterOffOn, - "PIROccupiedToUnoccupiedDelay": common_PIROccupiedToUnoccupiedDelay, - "PIRoccupancySensibility": common_PIR_occupancySensibility, - "occupancySensibility": common_PIR_occupancySensibility, - "UltrasonicOccupiedToUnoccupiedDelay": common_Ultrasnonic_OccupiedToUnoccupiedDelay, - "UltrasonicOccupancySensibility": common_Ultrasonic_occupancySensibility, "netatmoLedIfOn": legrand_enable_Led_IfOn_by_nwkid, "netatmoLedInDark": legrand_enable_Led_InDark_by_nwkid, "netatmoLedShutter": legrand_enable_Led_Shutter_by_nwkid, @@ -291,6 +282,9 @@ def ias_sensitivity(self, nwkid, sensitivity): def sanity_check_of_param(self, NwkId): self.log.logging("Heartbeat", "Debug", f"sanity_check_of_param {NwkId}") + DEVICE_PARAMETERS.update(ONOFF_DEVICE_PARAMETERS) + DEVICE_PARAMETERS.update(OCCUPANCY_DEVICE_PARAMETERS) + DEVICE_PARAMETERS.update(SONOFF_DEVICE_PARAMETERS) param_data = self.ListOfDevices.get(NwkId, {}).get("Param", {}) diff --git a/Modules/tuya.py b/Modules/tuya.py index 2f218c2c6..2c7dafb44 100644 --- a/Modules/tuya.py +++ b/Modules/tuya.py @@ -52,7 +52,12 @@ # 0x03: string # 0x04: enum8 ( 0x00-0xff) # 0x05: bitmap ( 1,2, 4 bytes) as bits - + +def is_tuya_switch_relay(self, nwkid): + model = self.ListOfDevices[nwkid].get("Model", "") + return model in ( "TS0601-switch", "TS0601-2Gangs-switch", "TS0601-Energy", ) + + def tuya_registration(self, nwkid, ty_data_request=False, parkside=False, tuya_registration_value=None): if "Model" not in self.ListOfDevices[nwkid]: return From 25829d3a07ddddeec83231f2d0557a4468439fe4 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Tue, 6 Feb 2024 16:57:24 +0100 Subject: [PATCH 06/21] Adding Tuya Color gradiant delay --- Modules/tuya.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Modules/tuya.py b/Modules/tuya.py index 2c7dafb44..23c7536a4 100644 --- a/Modules/tuya.py +++ b/Modules/tuya.py @@ -26,7 +26,7 @@ from Modules.bindings import bindDevice from Modules.domoMaj import MajDomoDevice from Modules.domoTools import Update_Battery_Device -from Modules.tools import (build_fcf, checkAndStoreAttributeValue, +from Modules.tools import (build_fcf, checkAndStoreAttributeValue,get_device_config_param, get_and_inc_ZCL_SQN, get_deviceconf_parameter_value, is_ack_tobe_disabled, updSQN) from Modules.tuyaConst import (TUYA_MANUF_CODE, TUYA_SMART_DOOR_LOCK_MODEL, @@ -1579,3 +1579,24 @@ def tuya_Move_To_Hue_Saturation_Brightness( self, NwkId, epout, hue, saturation, payload = "11" + sqn + "e1" + hue + saturation + brightness raw_APS_request(self, NwkId, epout, "0300", "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) + + +def tuya_color_grandiant(self, NwkId, epout, on_gradiant=None, off_gradiant=None): + """ Tuya specific command which allows to define the gradiant time to switch On and to Switch off """ + + self.log.logging("Tuya", "Debug", f"tuya_color_grandiant {NwkId}, {epout}, {on_gradiant}, {off_gradiant}", NwkId) + + on_gradiant = get_device_config_param( self, NwkId, "TuyaColorGradiantOnTime") or 10 + off_gradiant = get_device_config_param( self, NwkId, "TuyaColorGradiantOffTime") or 10 + + cmd = "fb" + sqn = get_and_inc_ZCL_SQN(self, NwkId) + + # gradiant should be expressed in tenth of second, while Tuya expect 1s = 1000 + on_gradiant *= 100 + off_gradiant *= 100 + + payload = "11" + sqn + cmd + "%08x" % on_gradiant + "%6x" %off_gradiant + + self.log.logging("Tuya", "Debug", f"tuya_color_grandiant {NwkId}, {epout}, payload: {payload}", NwkId) + raw_APS_request(self, NwkId, epout, "0300", "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) From 420a158e47e7e48e3fb3186f50d7536cee000191 Mon Sep 17 00:00:00 2001 From: Pipiche <8291674+pipiche38@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:03:55 +0100 Subject: [PATCH 07/21] Maj domo force ep (needed for Lumi decoupled mode) (#1710) * Set lumi.switch.acn047 in decoupled mode * Enable possibility to force MajDomoDevice on a specific Ep --- Classes/WebServer/WebServer.py | 2 +- Modules/readZclClusters.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Classes/WebServer/WebServer.py b/Classes/WebServer/WebServer.py index 226747465..9ee63697f 100644 --- a/Classes/WebServer/WebServer.py +++ b/Classes/WebServer/WebServer.py @@ -1295,7 +1295,7 @@ def rest_raw_zigbee(self, verb, data, parameters): 'timestamp': time.time() } - self.logging( "Log","Sending request to coordinator %s" % ( data)) + self.logging( "Log","Sending request to coordinator %s for target %s" % ( data, target_address)) self.ControllerLink.sendData( "RAW-COMMAND", data, NwkId=int(target_address,16), sqn=int(sqn,16), ackIsDisabled=ack_Is_Disabled ) return _response diff --git a/Modules/readZclClusters.py b/Modules/readZclClusters.py index fd75eb3d0..c55bf0759 100644 --- a/Modules/readZclClusters.py +++ b/Modules/readZclClusters.py @@ -376,7 +376,7 @@ def action_majdomodevice( self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, Msg self.log.logging( "ZclClusters", "Debug", "action_majdomodevice - %s/%s %s %s %s %s" %( MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgAttrID, device_model, value )) - + _majdomo_formater = cluster_attribute_retrieval( self, MsgSrcEp, MsgClusterId, MsgAttrID, "DomoDeviceFormat", model=device_model) self.log.logging( "ZclClusters", "Debug", " _majdomo_formater: %s" %_majdomo_formater) @@ -387,15 +387,18 @@ def action_majdomodevice( self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, Msg self.log.logging( "ZclClusters", "Debug", " _majdomo_formater: %s %s -> %s" %(_majdomo_formater, value, majValue)) _majdomo_cluster = cluster_attribute_retrieval( self, MsgSrcEp, MsgClusterId, MsgAttrID, "UpdDomoDeviceWithCluster", model=device_model) - self.log.logging( "ZclClusters", "Debug", " _majdomo_cluster: %s" %_majdomo_cluster) - majCluster = _majdomo_cluster if _majdomo_cluster is not None else MsgClusterId + self.log.logging( "ZclClusters", "Debug", " _majdomo_cluster: %s" %_majdomo_cluster) _majdomo_attribute = cluster_attribute_retrieval( self, MsgSrcEp, MsgClusterId, MsgAttrID, "UpdDomoDeviceWithAttribute", model=device_model) majAttribute = _majdomo_attribute if _majdomo_attribute is not None else "" self.log.logging( "ZclClusters", "Debug", " _majdomo_attribute: %s -> %s" %(_majdomo_attribute, majAttribute)) - MajDomoDevice(self, Devices, MsgSrcAddr, MsgSrcEp, majCluster, majValue, Attribute_=majAttribute) + _majdomo_endpoint = cluster_attribute_retrieval( self, MsgSrcEp, MsgClusterId, MsgAttrID, "UpdDomoDeviceWithEp", model=device_model) + target_ep = _majdomo_endpoint if _majdomo_endpoint is not None else MsgSrcEp + self.log.logging( "ZclClusters", "Debug", " _majdomo_ep: %s -> %s" %(_majdomo_endpoint, target_ep)) + + MajDomoDevice(self, Devices, MsgSrcAddr, target_ep, majCluster, majValue, Attribute_=majAttribute) def majdomodevice_possiblevalues( self, MsgSrcEp, MsgClusterId, MsgAttrID, model, value): From c509a15723ca17f4150353b4c8b976f516e0e11a Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Tue, 6 Feb 2024 18:03:23 +0100 Subject: [PATCH 08/21] refactoring of paramDevice, and get the settings distributed --- Modules/lumi.py | 21 ++++ Modules/paramDevice.py | 238 ++++--------------------------------- Modules/schneider_wiser.py | 7 ++ Modules/tuya.py | 33 +++++ Modules/tuyaSiren.py | 9 +- Modules/tuyaTRV.py | 19 +++ Modules/tuyaTS011F.py | 9 ++ 7 files changed, 123 insertions(+), 213 deletions(-) diff --git a/Modules/lumi.py b/Modules/lumi.py index aa514ff49..ffccd5e28 100644 --- a/Modules/lumi.py +++ b/Modules/lumi.py @@ -1089,3 +1089,24 @@ def store_lumi_attribute(self, NwkId, Attribute, Value): if "LUMI" not in self.ListOfDevices[NwkId]: self.ListOfDevices[NwkId]["LUMI"] = {} self.ListOfDevices[NwkId]["LUMI"][Attribute] = Value + + +LUMI_DEVICE_PARAMETERS = { + "vibrationAqarasensitivity": setXiaomiVibrationSensitivity, + "AqaraOpple_switch_power_outage_memory": xiaomi_switch_power_outage_memory, + "AqaraOpple_led_disabled_night": xiaomi_led_disabled_night, + "AqaraOpple_flip_indicator_light": xiaomi_flip_indicator_light, + "AqaraOpple_switch_operation_mode_opple": xiaomi_switch_operation_mode_opple, + "AqaraOpple_switch_operation_mode_opple_l1": xiaomi_switch_operation_mode_opple_l1, + "AqaraOpple_switch_operation_mode_opple_l2": xiaomi_switch_operation_mode_opple_l2, + "AqaraOpple_aqara_switch_mode_switch": xiaomi_aqara_switch_mode_switch, + "AqaraOppleMode": xiaomi_opple_mode, + "AqaraDetectionInterval": aqara_detection_interval, + "AqaraMultiClick": enable_click_mode_aqara, + "RTCZCGQ11LMMotionSensibility": RTCZCGQ11LM_motion_opple_sensitivity, + "RTCZCGQ11LMApproachDistance": RTCZCGQ11LM_motion_opple_approach_distance, + "RTCZCGQ11LMMonitoringMode": RTCZCGQ11LM_motion_opple_monitoring_mode, + "RTCGQ14LMTriggerIndicator": RTCGQ14LM_trigger_indicator, + + +} diff --git a/Modules/paramDevice.py b/Modules/paramDevice.py index 4acd1196a..18952c8b6 100644 --- a/Modules/paramDevice.py +++ b/Modules/paramDevice.py @@ -19,75 +19,24 @@ from DevicesModules.custom_sonoff import SONOFF_DEVICE_PARAMETERS from Modules.basicOutputs import (ballast_Configuration_max_level, - ballast_Configuration_min_level, - set_poweron_afteroffon) + ballast_Configuration_min_level) from Modules.danfoss import (danfoss_covered, danfoss_exercise_day_of_week, danfoss_exercise_trigger_time, danfoss_orientation, danfoss_viewdirection) -from Modules.enki import enki_set_poweron_after_offon_device from Modules.legrand_netatmo import (legrand_Dimmer_by_nwkid, legrand_enable_Led_IfOn_by_nwkid, legrand_enable_Led_InDark_by_nwkid, legrand_enable_Led_Shutter_by_nwkid) -from Modules.lumi import (RTCGQ14LM_trigger_indicator, - RTCZCGQ11LM_motion_opple_approach_distance, - RTCZCGQ11LM_motion_opple_monitoring_mode, - RTCZCGQ11LM_motion_opple_sensitivity, - aqara_detection_interval, enable_click_mode_aqara, - setXiaomiVibrationSensitivity, - xiaomi_aqara_switch_mode_switch, - xiaomi_flip_indicator_light, - xiaomi_led_disabled_night, xiaomi_opple_mode, - xiaomi_switch_operation_mode_opple, - xiaomi_switch_operation_mode_opple_l1, - xiaomi_switch_operation_mode_opple_l2, - xiaomi_switch_power_outage_memory) +from Modules.lumi import LUMI_DEVICE_PARAMETERS from Modules.occupancy_settings import OCCUPANCY_DEVICE_PARAMETERS from Modules.onoff_settings import ONOFF_DEVICE_PARAMETERS -from Modules.philips import (philips_led_indication, - philips_set_poweron_after_offon_device) -from Modules.readAttributes import ReadAttributeRequest_0006_400x -from Modules.schneider_wiser import (iTRV_open_window_detection, - wiser_home_lockout_thermostat, - wiser_lift_duration) -from Modules.tools import get_deviceconf_parameter_value, getEpForCluster -from Modules.tuya import (SmartRelayStatus01, SmartRelayStatus02, - SmartRelayStatus03, SmartRelayStatus04, - get_tuya_attribute, ts110e_light_type, - ts110e_switch01_type, ts110e_switch02_type, - tuya_backlight_command, tuya_cmd_ts004F, - tuya_curtain_mode, tuya_energy_childLock, - tuya_external_switch_mode, tuya_garage_run_time, - tuya_motion_zg204l_keeptime, - tuya_motion_zg204l_sensitivity, - tuya_pir_keep_time_lookup, - tuya_radar_motion_radar_detection_delay, - tuya_radar_motion_radar_fading_time, - tuya_radar_motion_radar_max_range, - tuya_radar_motion_radar_min_range, - tuya_radar_motion_sensitivity, - tuya_switch_indicate_light, tuya_switch_relay_status, - tuya_TS0004_back_light, tuya_TS0004_indicate_light, - tuya_window_cover_calibration, - tuya_window_cover_motor_reversal) -from Modules.tuyaSiren import (tuya_siren2_alarm_duration, - tuya_siren2_alarm_melody, - tuya_siren2_alarm_volume) -from Modules.tuyaTRV import (tuya_trv_boost_time, tuya_trv_calibration, - tuya_trv_child_lock, tuya_trv_eco_temp, - tuya_trv_holiday_setpoint, - tuya_trv_set_confort_temperature, - tuya_trv_set_eco_temperature, - tuya_trv_set_max_setpoint, - tuya_trv_set_min_setpoint, - tuya_trv_set_opened_window_temp, - tuya_trv_thermostat_sensor_mode, - tuya_trv_window_detection) -from Modules.tuyaTS011F import (tuya_ts011F_threshold_overCurrentBreaker, - tuya_ts011F_threshold_overPowerBreaker, - tuya_ts011F_threshold_overTemperatureBreaker, - tuya_ts011F_threshold_overVoltageBreaker, - tuya_ts011F_threshold_underVoltageBreaker) +from Modules.philips import philips_led_indication +from Modules.schneider_wiser import SCHNEIDER_DEVICE_PARAMETERS +from Modules.tools import getEpForCluster +from Modules.tuya import TUYA_DEVICE_PARAMETERS +from Modules.tuyaSiren import TUYA_SIREN_DEVICE_PARAMETERS +from Modules.tuyaTRV import TUYA_TRV_DEVICE_PARAMETERS +from Modules.tuyaTS011F import TUYA_TS011F_DEVICE_PARAMETERS from Modules.tuyaTS0601 import ts0601_extract_data_point_infos, ts0601_settings @@ -99,90 +48,6 @@ def Ballast_min_level(self, nwkid, min_level): ballast_Configuration_min_level(self, nwkid, min_level) -def param_PowerOnAfterOffOn(self, nwkid, mode): - # 0 - stay Off after a Off/On - # 1 - stay On after a Off/On - # 255 - stay to previous state after a Off/On ( or 2 for BlitzWolf) - - model = "" - if "Model" in self.ListOfDevices[nwkid] and self.ListOfDevices[nwkid]["Model"]: - model = self.ListOfDevices[nwkid] and self.ListOfDevices[nwkid]["Model"] - - self.log.logging("Heartbeat", "Debug", "param_PowerOnAfterOffOn for %s mode: %s for model: %s " % (nwkid, mode, model), nwkid) - if int(mode) not in (0, 1, 2, 255): - return - - if "Manufacturer" not in self.ListOfDevices[nwkid]: - return - - if ( - self.ListOfDevices[nwkid]["Manufacturer"] == "100b" # Philips - or model == "FNB56-ZCW25FB2.1" - ): - if not _check_attribute_exist( self, nwkid, "0b", "0006", "4003"): - return - - if self.ListOfDevices[nwkid]["Ep"]["0b"]["0006"]["4003"] != str(mode): - self.log.logging("Heartbeat", "Debug", "param_PowerOnAfterOffOn for Philips for %s mode: %s" % (nwkid, mode), nwkid) - philips_set_poweron_after_offon_device(self, mode, nwkid) - ReadAttributeRequest_0006_400x(self, nwkid) - - elif get_deviceconf_parameter_value(self, model, "PowerOnOffStateAttribute8002", return_default=False): - if not _check_attribute_exist( self, nwkid, "01", "0006", "8002"): - return - if self.ListOfDevices[nwkid]["Ep"]["01"]["0006"]["8002"] == "2" and str(mode) == "255": - return - if self.ListOfDevices[nwkid]["Ep"]["01"]["0006"]["8002"] != str(mode): - self.log.logging("Heartbeat", "Debug", "param_PowerOnAfterOffOn for Tuya for %s mode: %s" % (nwkid, mode), nwkid) - set_poweron_afteroffon(self, nwkid, mode) - ReadAttributeRequest_0006_400x(self, nwkid) - - elif self.ListOfDevices[nwkid]["Manufacturer"] == "1277": # Enki Leroy Merlin - if not _check_attribute_exist( self, nwkid, "01", "0006", "4003"): - return - - if self.ListOfDevices[nwkid]["Ep"]["01"]["0006"]["4003"] != str(mode): - self.log.logging("Heartbeat", "Debug", "param_PowerOnAfterOffOn for Enki for %s mode: %s" % (nwkid, mode), nwkid) - enki_set_poweron_after_offon_device(self, mode, nwkid) - ReadAttributeRequest_0006_400x(self, nwkid) - - elif model in ( - "TS0601-switch", - "TS0601-2Gangs-switch", - "TS0601-Energy", - ): - if get_tuya_attribute(self, nwkid, "RelayStatus") != mode: - tuya_switch_relay_status(self, nwkid, mode) - - else: - # Ikea, Legrand, - for ep in self.ListOfDevices[nwkid]["Ep"]: - if not _check_attribute_exist( self, nwkid, ep, "0006", "4003"): - continue - - if self.ListOfDevices[nwkid]["Ep"][ep]["0006"]["4003"] == str(mode): - continue - elif _check_attribute_exist( self, nwkid, ep, "0006", "8002") and self.ListOfDevices[nwkid]["Ep"][ep]["0006"]["8002"] == str(mode): - continue - - self.log.logging("Heartbeat", "Debug", "param_PowerOnAfterOffOn for %s mode: %s" % (nwkid, mode), nwkid) - set_poweron_afteroffon(self, nwkid, mode) - ReadAttributeRequest_0006_400x(self, nwkid) - - -def _check_attribute_exist( self, nwkid, ep, cluster, attribute): - if ep not in self.ListOfDevices[nwkid]["Ep"]: - self.log.logging("Heartbeat", "Debug", "No ep: %s" %ep, nwkid) - return False - if cluster not in self.ListOfDevices[nwkid]["Ep"][ ep ]: - self.log.logging("Heartbeat", "Debug", "No Cluster: %s" %cluster, nwkid) - return False - if attribute not in self.ListOfDevices[nwkid]["Ep"][ ep ][ cluster ]: - self.log.logging("Heartbeat", "Debug", "No Attribute: %s" %attribute, nwkid) - return False - return True - - def ias_wd_sirene_max_alarm_dureation( self, nwkid, duration): if self.iaszonemgt: Epout = getEpForCluster(self, nwkid, "0502", strict=True) @@ -194,99 +59,48 @@ def ias_sensitivity(self, nwkid, sensitivity): if self.iaszonemgt: self.iaszonemgt.ias_sensitivity( nwkid, sensitivity) + + DEVICE_PARAMETERS = { "HueLedIndication": philips_led_indication, "netatmoLedIfOn": legrand_enable_Led_IfOn_by_nwkid, "netatmoLedInDark": legrand_enable_Led_InDark_by_nwkid, "netatmoLedShutter": legrand_enable_Led_Shutter_by_nwkid, "netatmoEnableDimmer": legrand_Dimmer_by_nwkid, - "SensorMode": tuya_trv_thermostat_sensor_mode, - "LightIndicator": tuya_switch_indicate_light, - "TuyaEnergyChildLock": tuya_energy_childLock, "BallastMaxLevel": Ballast_max_level, "BallastMinLevel": Ballast_min_level, - "WiserLockThermostat": wiser_home_lockout_thermostat, - "WiseriTrvWindowOpen": iTRV_open_window_detection, - "TuyaMotoReversal": tuya_window_cover_motor_reversal, - "TuyaBackLight": tuya_backlight_command, - "TuyaCurtainMode": tuya_curtain_mode, - "TuyaCalibrationTime": tuya_window_cover_calibration, "eTRVExerciseDay": danfoss_exercise_day_of_week, "eTRVExerciseTime": danfoss_exercise_trigger_time, "DanfossCovered": danfoss_covered, "DanfossTRVOrientation": danfoss_orientation, "DanfossViewDirection": danfoss_viewdirection, - "TS004FMode": tuya_cmd_ts004F, - "vibrationAqarasensitivity": setXiaomiVibrationSensitivity, - "BRT100WindowsDetection": tuya_trv_window_detection, - "BRT100ChildLock": tuya_trv_child_lock, - "TuyaTRV5_ChildLock": tuya_trv_child_lock, - "TuyaTRV5_EcoTemp": tuya_trv_set_eco_temperature, - "TuyaTRV5_ConfortTemp": tuya_trv_set_confort_temperature, - "TuyaTRV5_OpenedWindowTemp": tuya_trv_set_opened_window_temp, - "BRT100BoostDuration": tuya_trv_boost_time, - "TuyaTRV5_BoostTime": tuya_trv_boost_time, - "TuyaTRV5_Calibration": tuya_trv_calibration, - "TuyaTRV5_HolidaySetPoint": tuya_trv_holiday_setpoint, - "BRT100Calibration": tuya_trv_calibration, - "BRT100SetpointEco": tuya_trv_eco_temp, - "BRT100MaxSetpoint": tuya_trv_set_max_setpoint, - "BRT100MinSetpoint": tuya_trv_set_min_setpoint, - "moesCalibrationTime": tuya_window_cover_calibration, - "TuyaAlarmLevel": tuya_siren2_alarm_volume, - "TuyaAlarmDuration": tuya_siren2_alarm_duration, - "TuyaAlarmMelody": tuya_siren2_alarm_melody, "SireneMaxAlarmDuration": ias_wd_sirene_max_alarm_dureation, - "TuyaGarageOpenerRunTime": tuya_garage_run_time, - "TuyaSwitchMode": tuya_external_switch_mode, - "SmartSwitchBackLight": tuya_TS0004_back_light, - "SmartSwitchIndicateLight": tuya_TS0004_indicate_light, - "SmartRelayStatus01": SmartRelayStatus01, - "SmartRelayStatus02": SmartRelayStatus02, - "SmartRelayStatus03": SmartRelayStatus03, - "SmartRelayStatus04": SmartRelayStatus04, - "RTCZCGQ11LMMotionSensibility": RTCZCGQ11LM_motion_opple_sensitivity, - "RTCZCGQ11LMApproachDistance": RTCZCGQ11LM_motion_opple_approach_distance, - "RTCZCGQ11LMMonitoringMode": RTCZCGQ11LM_motion_opple_monitoring_mode, - "ZG204Z_MotionSensivity": tuya_motion_zg204l_sensitivity, - "ZG204Z_MotionOccupancyTime": tuya_motion_zg204l_keeptime, - "RadarMotionSensitivity": tuya_radar_motion_sensitivity, - "RadarMotionMinRange": tuya_radar_motion_radar_min_range, - "RadarMotionMaxRange": tuya_radar_motion_radar_max_range, - "RadarMotionDelay": tuya_radar_motion_radar_detection_delay, - "RadarMotionFading": tuya_radar_motion_radar_fading_time, - "AqaraOpple_switch_power_outage_memory": xiaomi_switch_power_outage_memory, - "AqaraOpple_led_disabled_night": xiaomi_led_disabled_night, - "AqaraOpple_flip_indicator_light": xiaomi_flip_indicator_light, - "AqaraOpple_switch_operation_mode_opple": xiaomi_switch_operation_mode_opple, - "AqaraOpple_switch_operation_mode_opple_l1": xiaomi_switch_operation_mode_opple_l1, - "AqaraOpple_switch_operation_mode_opple_l2": xiaomi_switch_operation_mode_opple_l2, - "AqaraOpple_aqara_switch_mode_switch": xiaomi_aqara_switch_mode_switch, - "AqaraOppleMode": xiaomi_opple_mode, - "TuyaPIRKeepTime": tuya_pir_keep_time_lookup, "IASsensitivity": ias_sensitivity, - "RTCGQ14LMTriggerIndicator": RTCGQ14LM_trigger_indicator, - "AqaraDetectionInterval": aqara_detection_interval, - "WiserShutterDuration": wiser_lift_duration, - "AqaraMultiClick": enable_click_mode_aqara, - "TS110ELightType": ts110e_light_type, - "TS110ESwitch01Type": ts110e_switch01_type, - "TS110ESwitch02Type": ts110e_switch02_type, - "TS011F_overTemperatureBreaker": tuya_ts011F_threshold_overTemperatureBreaker, - "TS011F_overPowerBreaker": tuya_ts011F_threshold_overPowerBreaker, - "TS011F_overCurrentBreeaker": tuya_ts011F_threshold_overCurrentBreaker, - "TS011F_overVoltageBreaker": tuya_ts011F_threshold_overVoltageBreaker, - "TS011F_underVoltageBreaker": tuya_ts011F_threshold_underVoltageBreaker } + + + def sanity_check_of_param(self, NwkId): self.log.logging("Heartbeat", "Debug", f"sanity_check_of_param {NwkId}") + + # Load specific settings DEVICE_PARAMETERS.update(ONOFF_DEVICE_PARAMETERS) DEVICE_PARAMETERS.update(OCCUPANCY_DEVICE_PARAMETERS) + # Load Manufacturer specific settings DEVICE_PARAMETERS.update(SONOFF_DEVICE_PARAMETERS) + DEVICE_PARAMETERS.update(TUYA_DEVICE_PARAMETERS) + DEVICE_PARAMETERS.update(TUYA_TS011F_DEVICE_PARAMETERS) + DEVICE_PARAMETERS.update(TUYA_TRV_DEVICE_PARAMETERS) + DEVICE_PARAMETERS.update(TUYA_SIREN_DEVICE_PARAMETERS) + + DEVICE_PARAMETERS.update(SCHNEIDER_DEVICE_PARAMETERS) + + DEVICE_PARAMETERS.update(LUMI_DEVICE_PARAMETERS) + param_data = self.ListOfDevices.get(NwkId, {}).get("Param", {}) model_name = self.ListOfDevices.get(NwkId, {}).get("Model", "") diff --git a/Modules/schneider_wiser.py b/Modules/schneider_wiser.py index 43e351554..560d07f7f 100755 --- a/Modules/schneider_wiser.py +++ b/Modules/schneider_wiser.py @@ -2066,3 +2066,10 @@ def get_local_temperature_from_wiserroom( self, NwkId, room=None): def wiser_lift_duration( self, nwkid, duration): if 0 < duration < 300: write_attribute( self, nwkid, ZIGATE_EP, "05", "0102", "105e", "01", "e000", "21", "%04x" % duration if 0 < duration < 300 else 120, ackIsDisabled=False) + + +SCHNEIDER_DEVICE_PARAMETERS = { + "WiserLockThermostat": wiser_home_lockout_thermostat, + "WiseriTrvWindowOpen": iTRV_open_window_detection, + "WiserShutterDuration": wiser_lift_duration, +} diff --git a/Modules/tuya.py b/Modules/tuya.py index 23c7536a4..65c80c6c0 100644 --- a/Modules/tuya.py +++ b/Modules/tuya.py @@ -1600,3 +1600,36 @@ def tuya_color_grandiant(self, NwkId, epout, on_gradiant=None, off_gradiant=None self.log.logging("Tuya", "Debug", f"tuya_color_grandiant {NwkId}, {epout}, payload: {payload}", NwkId) raw_APS_request(self, NwkId, epout, "0300", "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) + + +TUYA_DEVICE_PARAMETERS = { + "TuyaColorGradiantOnTime": tuya_color_grandiant, + "TuyaColorGradiantOffTime": tuya_color_grandiant, + "LightIndicator": tuya_switch_indicate_light, + "TuyaEnergyChildLock": tuya_energy_childLock, + "TuyaMotoReversal": tuya_window_cover_motor_reversal, + "TuyaBackLight": tuya_backlight_command, + "TuyaCurtainMode": tuya_curtain_mode, + "TuyaCalibrationTime": tuya_window_cover_calibration, + "TS004FMode": tuya_cmd_ts004F, + "moesCalibrationTime": tuya_window_cover_calibration, + "TuyaGarageOpenerRunTime": tuya_garage_run_time, + "TuyaSwitchMode": tuya_external_switch_mode, + "SmartSwitchBackLight": tuya_TS0004_back_light, + "SmartSwitchIndicateLight": tuya_TS0004_indicate_light, + "SmartRelayStatus01": SmartRelayStatus01, + "SmartRelayStatus02": SmartRelayStatus02, + "SmartRelayStatus03": SmartRelayStatus03, + "SmartRelayStatus04": SmartRelayStatus04, + "ZG204Z_MotionSensivity": tuya_motion_zg204l_sensitivity, + "ZG204Z_MotionOccupancyTime": tuya_motion_zg204l_keeptime, + "RadarMotionSensitivity": tuya_radar_motion_sensitivity, + "RadarMotionMinRange": tuya_radar_motion_radar_min_range, + "RadarMotionMaxRange": tuya_radar_motion_radar_max_range, + "RadarMotionDelay": tuya_radar_motion_radar_detection_delay, + "RadarMotionFading": tuya_radar_motion_radar_fading_time, + "TuyaPIRKeepTime": tuya_pir_keep_time_lookup, + "TS110ELightType": ts110e_light_type, + "TS110ESwitch01Type": ts110e_switch01_type, + "TS110ESwitch02Type": ts110e_switch02_type, +} diff --git a/Modules/tuyaSiren.py b/Modules/tuyaSiren.py index 6756c6246..3d945f315 100644 --- a/Modules/tuyaSiren.py +++ b/Modules/tuyaSiren.py @@ -488,6 +488,7 @@ def tuya_siren2_alarm_duration(self, nwkid, duration): data = "%08x" % duration tuya_cmd(self, nwkid, EPout, cluster_frame, sqn, cmd, action, data) + def tuya_siren2_alarm_melody(self, nwkid, melody): # duration in second @@ -502,7 +503,6 @@ def tuya_siren2_alarm_melody(self, nwkid, melody): - def tuya_siren2_trigger(self, nwkid, onoff): self.log.logging("Tuya", "Debug", "tuya_siren2_trigger - %s onoff: %s" % (nwkid, onoff)) @@ -515,3 +515,10 @@ def tuya_siren2_trigger(self, nwkid, onoff): action = "%04x" % struct.unpack("H", struct.pack(">H", 0x010d))[0] data = onoff tuya_cmd(self, nwkid, EPout, cluster_frame, sqn, cmd, action, data) + + +TUYA_SIREN_DEVICE_PARAMETERS = { + "TuyaAlarmLevel": tuya_siren2_alarm_volume, + "TuyaAlarmDuration": tuya_siren2_alarm_duration, + "TuyaAlarmMelody": tuya_siren2_alarm_melody, +} diff --git a/Modules/tuyaTRV.py b/Modules/tuyaTRV.py index d9653cca4..e76f21822 100644 --- a/Modules/tuyaTRV.py +++ b/Modules/tuyaTRV.py @@ -1552,3 +1552,22 @@ def get_datapoint_command(self, nwkid, cmd): ) return None return eTRV_MATRIX[_model_name]["ToDevice"][cmd] + + +TUYA_TRV_DEVICE_PARAMETERS = { + "SensorMode": tuya_trv_thermostat_sensor_mode, + "BRT100WindowsDetection": tuya_trv_window_detection, + "BRT100ChildLock": tuya_trv_child_lock, + "TuyaTRV5_ChildLock": tuya_trv_child_lock, + "TuyaTRV5_EcoTemp": tuya_trv_set_eco_temperature, + "TuyaTRV5_ConfortTemp": tuya_trv_set_confort_temperature, + "TuyaTRV5_OpenedWindowTemp": tuya_trv_set_opened_window_temp, + "BRT100BoostDuration": tuya_trv_boost_time, + "TuyaTRV5_BoostTime": tuya_trv_boost_time, + "TuyaTRV5_Calibration": tuya_trv_calibration, + "TuyaTRV5_HolidaySetPoint": tuya_trv_holiday_setpoint, + "BRT100Calibration": tuya_trv_calibration, + "BRT100SetpointEco": tuya_trv_eco_temp, + "BRT100MaxSetpoint": tuya_trv_set_max_setpoint, + "BRT100MinSetpoint": tuya_trv_set_min_setpoint, +} diff --git a/Modules/tuyaTS011F.py b/Modules/tuyaTS011F.py index e53b4a361..b7960927e 100644 --- a/Modules/tuyaTS011F.py +++ b/Modules/tuyaTS011F.py @@ -137,3 +137,12 @@ def tuya_ts011F_e7(self, NwkId, ): self.log.logging("TuyaTS011F", "Debug", f"tuya_ts011F_e7 {payload}", NwkId) raw_APS_request(self, NwkId, "01", "e001", "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) + + +TUYA_TS011F_DEVICE_PARAMETERS = { + "TS011F_overTemperatureBreaker": tuya_ts011F_threshold_overTemperatureBreaker, + "TS011F_overPowerBreaker": tuya_ts011F_threshold_overPowerBreaker, + "TS011F_overCurrentBreeaker": tuya_ts011F_threshold_overCurrentBreaker, + "TS011F_overVoltageBreaker": tuya_ts011F_threshold_overVoltageBreaker, + "TS011F_underVoltageBreaker": tuya_ts011F_threshold_underVoltageBreaker +} From 5d8147957a2adf534eb20a77f2239b99983f7567 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Tue, 6 Feb 2024 18:43:13 +0100 Subject: [PATCH 09/21] update some settings --- Classes/PluginConf.py | 1 + Modules/paramDevice.py | 5 +--- Modules/tuya.py | 55 +++++++++++++++++++++--------------------- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/Classes/PluginConf.py b/Classes/PluginConf.py index 6ed168676..eaaf6d0f9 100644 --- a/Classes/PluginConf.py +++ b/Classes/PluginConf.py @@ -302,6 +302,7 @@ "onoffSettings": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, "outRawAPS": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, "showTimeOutMsg": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, + "tuyaSettings": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True }, "trackTransportError": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, "trackZclClustersIn": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, "trackZclClustersOut": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": False }, diff --git a/Modules/paramDevice.py b/Modules/paramDevice.py index 18952c8b6..c48c91591 100644 --- a/Modules/paramDevice.py +++ b/Modules/paramDevice.py @@ -60,7 +60,6 @@ def ias_sensitivity(self, nwkid, sensitivity): self.iaszonemgt.ias_sensitivity( nwkid, sensitivity) - DEVICE_PARAMETERS = { "HueLedIndication": philips_led_indication, "netatmoLedIfOn": legrand_enable_Led_IfOn_by_nwkid, @@ -78,9 +77,7 @@ def ias_sensitivity(self, nwkid, sensitivity): "IASsensitivity": ias_sensitivity, } - - - + def sanity_check_of_param(self, NwkId): self.log.logging("Heartbeat", "Debug", f"sanity_check_of_param {NwkId}") diff --git a/Modules/tuya.py b/Modules/tuya.py index 65c80c6c0..240b36aa7 100644 --- a/Modules/tuya.py +++ b/Modules/tuya.py @@ -548,7 +548,7 @@ def tuya_energy_childLock(self, NwkId, lock=0x01): cmd = "00" # Command action = "1d01" data = "%02x" % lock - self.log.logging("Tuya", "Debug", "tuya_energy_childLock - action: %s data: %s" % (action, data)) + self.log.logging("tuyaSettings", "Debug", "tuya_energy_childLock - action: %s data: %s" % (action, data)) tuya_cmd(self, NwkId, EPout, cluster_frame, sqn, cmd, action, data) @@ -556,10 +556,10 @@ def tuya_switch_indicate_light(self, NwkId, light=0x01): # 0005 0f 04 0001 00 -- Indicate Off # 0004 0f 04 0001 01 -- Indicate Switch ( On when On) # 0006 0f 04 0001 02 -- Indicate Position (on when Off ) - self.log.logging("Tuya", "Debug", "tuya_switch_indicate_light - %s Light: %s" % (NwkId, light), NwkId) + self.log.logging("tuyaSettings", "Debug", "tuya_switch_indicate_light - %s Light: %s" % (NwkId, light), NwkId) # determine which Endpoint if light not in (0x00, 0x01, 0x02): - self.log.logging("Tuya", "Error", "tuya_switch_indicate_light - Unexpected light: %s" % light) + self.log.logging("tuyaSettings", "Error", "tuya_switch_indicate_light - Unexpected light: %s" % light) return EPout = "01" @@ -568,7 +568,7 @@ def tuya_switch_indicate_light(self, NwkId, light=0x01): cmd = "00" # Command action = "0f04" data = "%02x" % light - self.log.logging("Tuya", "Debug", "tuya_switch_indicate_light - action: %s data: %s" % (action, data)) + self.log.logging("tuyaSettings", "Debug", "tuya_switch_indicate_light - action: %s data: %s" % (action, data)) tuya_cmd(self, NwkId, EPout, cluster_frame, sqn, cmd, action, data) @@ -852,10 +852,10 @@ def tuya_dimmer_dimmer(self, NwkId, srcEp, percent): # Tuya Smart Cover Switch def tuya_window_cover_calibration(self, nwkid, duration): # (0x0102) | Write Attributes (0x02) | 0xf003 | 0x21 16-Bit Unsigned Int | 600 0x0258) | 68 s - self.log.logging( "Tuya", "Debug", "tuya_window_cover_calibration - Nwkid: %s Calibration %s" % ( + self.log.logging( "tuyaSettings", "Debug", "tuya_window_cover_calibration - Nwkid: %s Calibration %s" % ( nwkid, duration), nwkid, ) - self.log.logging( "Tuya", "Debug", "tuya_window_cover_calibration - duration %s" % ( duration), nwkid, ) + self.log.logging( "tuyaSettings", "Debug", "tuya_window_cover_calibration - duration %s" % ( duration), nwkid, ) write_attribute(self, nwkid, ZIGATE_EP, "01", "0102", "0000", "00", "f003", "21", "%04x" %duration, ackIsDisabled=False) @@ -1093,7 +1093,7 @@ def tuya_smart_motion_all_in_one(self, Devices, _ModelName, NwkId, srcEp, Cluste def tuya_pir_keep_time_lookup( self, nwkid, keeptime): keeptime = min( keeptime // 30, 2) - self.log.logging("Tuya", "Debug", "tuya_pir_keep_time_lookup - keeptime duration %s secondes" % keeptime, nwkid) + self.log.logging("tuyaSettings", "Debug", "tuya_pir_keep_time_lookup - keeptime duration %s secondes" % keeptime, nwkid) EPout = "01" write_attribute(self, nwkid, ZIGATE_EP, EPout, "0500", "0000", "00", "f001", "20", "%02x" %keeptime, ackIsDisabled=False) @@ -1140,7 +1140,7 @@ def tuya_garage_door_action( self, NwkId, onoff): def tuya_garage_run_time(self, NwkId, duration): # 0006/0402/0004/0000001e 30 secondes # 0007/0402/0004/0000003c 60 secondes - self.log.logging("Tuya", "Debug", "tuya_garage_run_time - duration %s" % duration, NwkId) + self.log.logging("tuyaSettings", "Debug", "tuya_garage_run_time - duration %s" % duration, NwkId) EPout = "01" sqn = get_and_inc_ZCL_SQN(self, NwkId) cluster_frame = "11" @@ -1176,9 +1176,9 @@ def tuya_garage_timeout(self, NwkId, duration): def tuya_external_switch_mode( self, NwkId, mode): - self.log.logging("Tuya", "Debug", "tuya_external_switch_mode - mode %s" % mode, NwkId) + self.log.logging("tuyaSettings", "Debug", "tuya_external_switch_mode - mode %s" % mode, NwkId) if mode not in TUYA_SWITCH_MODE: - self.log.logging("Tuya", "Debug", "tuya_external_switch_mode - mode %s undefined" % mode, NwkId) + self.log.logging("tuyaSettings", "Debug", "tuya_external_switch_mode - mode %s undefined" % mode, NwkId) return EPout = "01" mode = "%02x" %TUYA_SWITCH_MODE[mode] @@ -1365,7 +1365,7 @@ def tuya_motion_response(self, Devices, _ModelName, NwkId, srcEp, ClusterID, dst def tuya_motion_zg204l_sensitivity(self, nwkid, sensitivity): # {'low': 0, 'medium': 1, 'high': 2} - self.log.logging("Tuya", "Debug", "tuya_motion_zg204l_keeptime - %s mode: %s" % (nwkid, sensitivity)) + self.log.logging("tuyaSettings", "Debug", "tuya_motion_zg204l_keeptime - %s mode: %s" % (nwkid, sensitivity)) if sensitivity not in (0x00, 0x01, 0x02): return sqn = get_and_inc_ZCL_SQN(self, nwkid) @@ -1381,7 +1381,7 @@ def tuya_motion_zg204l_sensitivity(self, nwkid, sensitivity): def tuya_motion_zg204l_keeptime(self, nwkid, keep_time): # {'10': 0, '30': 1, '60': 2, '120': 3} - self.log.logging("Tuya", "Debug", "tuya_motion_zg204l_keeptime - %s mode: %s" % (nwkid, keep_time)) + self.log.logging("tuyaSettings", "Debug", "tuya_motion_zg204l_keeptime - %s mode: %s" % (nwkid, keep_time)) if keep_time not in (0x00, 0x01, 0x02, 0x03 ): return sqn = get_and_inc_ZCL_SQN(self, nwkid) @@ -1397,9 +1397,9 @@ def tuya_motion_zg204l_keeptime(self, nwkid, keep_time): def tuya_radar_motion_sensitivity(self, nwkid, mode): # 00/35/0202000400000000 - self.log.logging("Tuya", "Debug", "tuya_radar_motion_sensitivity - %s mode: %s" % (nwkid, mode)) + self.log.logging("tuyaSettings", "Debug", "tuya_radar_motion_sensitivity - %s mode: %s" % (nwkid, mode)) if mode > 7 and mode < 0 : - self.log.logging("Tuya", "Error", "tuya_radar_motion_sensitivity - %s Invalid sensitivity: %s" % (nwkid, mode)) + self.log.logging("tuyaSettings", "Error", "tuya_radar_motion_sensitivity - %s Invalid sensitivity: %s" % (nwkid, mode)) return sqn = get_and_inc_ZCL_SQN(self, nwkid) @@ -1414,7 +1414,7 @@ def tuya_radar_motion_sensitivity(self, nwkid, mode): def tuya_radar_motion_radar_min_range(self, nwkid, mode): - self.log.logging("Tuya", "Debug", "tuya_radar_motion_radar_min_range - %s mode: %s" % (nwkid, mode)) + self.log.logging("tuyaSettings", "Debug", "tuya_radar_motion_radar_min_range - %s mode: %s" % (nwkid, mode)) sqn = get_and_inc_ZCL_SQN(self, nwkid) action = "%02x02" % 0x03 @@ -1428,9 +1428,9 @@ def tuya_radar_motion_radar_min_range(self, nwkid, mode): def tuya_radar_motion_radar_max_range(self, nwkid, mode): - self.log.logging("Tuya", "Debug", "tuya_radar_motion_radar_max_range - %s mode: %s" % (nwkid, mode)) + self.log.logging("tuyaSettings", "Debug", "tuya_radar_motion_radar_max_range - %s mode: %s" % (nwkid, mode)) if mode > ( 10 * 100): - self.log.logging("Tuya", "Error", "tuya_radar_motion_radar_max_range - %s Invalid max range: %s cm" % (nwkid, mode)) + self.log.logging("tuyaSettings", "Error", "tuya_radar_motion_radar_max_range - %s Invalid max range: %s cm" % (nwkid, mode)) return sqn = get_and_inc_ZCL_SQN(self, nwkid) @@ -1446,9 +1446,9 @@ def tuya_radar_motion_radar_max_range(self, nwkid, mode): def tuya_radar_motion_radar_detection_delay(self, nwkid, mode): - self.log.logging("Tuya", "Debug", "tuya_radar_motion_radar_detection_delay - %s mode: %s" % (nwkid, mode)) + self.log.logging("tuyaSettings", "Debug", "tuya_radar_motion_radar_detection_delay - %s mode: %s" % (nwkid, mode)) if mode > 100 and mode < 0: - self.log.logging("Tuya", "Error", "tuya_radar_motion_radar_detection_delay - %s Invalid delay: %s" % (nwkid, mode)) + self.log.logging("tuyaSettings", "Error", "tuya_radar_motion_radar_detection_delay - %s Invalid delay: %s" % (nwkid, mode)) return sqn = get_and_inc_ZCL_SQN(self, nwkid) @@ -1464,9 +1464,9 @@ def tuya_radar_motion_radar_detection_delay(self, nwkid, mode): def tuya_radar_motion_radar_fading_time(self, nwkid, mode): - self.log.logging("Tuya", "Debug", "tuya_radar_motion_radar_fading_time - %s mode: %s" % (nwkid, mode)) + self.log.logging("tuyaSettings", "Debug", "tuya_radar_motion_radar_fading_time - %s mode: %s" % (nwkid, mode)) if mode > 15000 and mode < 0: - self.log.logging("Tuya", "Error", "tuya_radar_motion_radar_fading_time - %s Invalid delay: %s" % (nwkid, mode)) + self.log.logging("tuyaSettings", "Error", "tuya_radar_motion_radar_fading_time - %s Invalid delay: %s" % (nwkid, mode)) return sqn = get_and_inc_ZCL_SQN(self, nwkid) @@ -1514,7 +1514,7 @@ def tuya_smart_door_lock(self, Devices, _ModelName, NwkId, srcEp, ClusterID, dst def ts110e_light_type( self, NwkId, mode): # led: 0, incandescent: 1, halogen: 2 - self.log.logging("Tuya", "Debug", "ts110e_light_type - mode %s" % mode, NwkId) + self.log.logging("tuyaSettings", "Debug", "ts110e_light_type - mode %s" % mode, NwkId) EPout = "01" mode = "%02x" %mode write_attribute(self, NwkId, ZIGATE_EP, EPout, "0008", "0000", "00", "fc02", "20", mode, ackIsDisabled=False) @@ -1527,7 +1527,7 @@ def ts110e_switch02_type( self, NwkId, mode): def ts110e_switch_type( self, NwkId, EPout, mode): # momentary: 0, toggle: 1, state: 2 - self.log.logging("Tuya", "Debug", "ts110e_switch_type - mode %s" % mode, NwkId) + self.log.logging("tuyaSettings", "Debug", "ts110e_switch_type - mode %s" % mode, NwkId) mode = "%02x" %mode write_attribute(self, NwkId, ZIGATE_EP, EPout, "0008", "0000", "00", "fc02", "20", mode, ackIsDisabled=False) @@ -1581,24 +1581,25 @@ def tuya_Move_To_Hue_Saturation_Brightness( self, NwkId, epout, hue, saturation, raw_APS_request(self, NwkId, epout, "0300", "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) -def tuya_color_grandiant(self, NwkId, epout, on_gradiant=None, off_gradiant=None): +def tuya_color_grandiant(self, NwkId, on_gradiant=None, off_gradiant=None): """ Tuya specific command which allows to define the gradiant time to switch On and to Switch off """ - self.log.logging("Tuya", "Debug", f"tuya_color_grandiant {NwkId}, {epout}, {on_gradiant}, {off_gradiant}", NwkId) + self.log.logging("tuyaSettings", "Debug", f"tuya_color_grandiant {NwkId}, {on_gradiant}, {off_gradiant}", NwkId) on_gradiant = get_device_config_param( self, NwkId, "TuyaColorGradiantOnTime") or 10 off_gradiant = get_device_config_param( self, NwkId, "TuyaColorGradiantOffTime") or 10 cmd = "fb" + epout = "01" sqn = get_and_inc_ZCL_SQN(self, NwkId) # gradiant should be expressed in tenth of second, while Tuya expect 1s = 1000 on_gradiant *= 100 off_gradiant *= 100 - payload = "11" + sqn + cmd + "%08x" % on_gradiant + "%6x" %off_gradiant + payload = "11" + sqn + cmd + "%08x" % on_gradiant + "%06x" %off_gradiant - self.log.logging("Tuya", "Debug", f"tuya_color_grandiant {NwkId}, {epout}, payload: {payload}", NwkId) + self.log.logging("tuyaSettings", "Debug", f"tuya_color_grandiant {NwkId}, {epout}, payload: {payload}", NwkId) raw_APS_request(self, NwkId, epout, "0300", "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) From 7c4fc0fe868685bf1125599bebb9f4a742d0ad52 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sat, 10 Feb 2024 12:41:26 +0100 Subject: [PATCH 10/21] make sure that the payload size is correct before extracting data. Seems that Aqara door sensor do not always send the right payload --- Z4D_decoders/z4d_decoder_Active_Ep_Rsp.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Z4D_decoders/z4d_decoder_Active_Ep_Rsp.py b/Z4D_decoders/z4d_decoder_Active_Ep_Rsp.py index 7bfad00f2..2e41cc5d6 100644 --- a/Z4D_decoders/z4d_decoder_Active_Ep_Rsp.py +++ b/Z4D_decoders/z4d_decoder_Active_Ep_Rsp.py @@ -17,22 +17,21 @@ def Decode8045(self, Devices, MsgData, MsgLQI): - MsgDataSQN, MsgDataStatus, MsgDataShAddr, MsgDataEpCount, MsgDataEPlist = ( - MsgData[:2], MsgData[2:4], MsgData[4:8], MsgData[8:10], MsgData[10:] - ) - - self.log.logging('Pairing', 'Debug', f'Decode8045 - Reception Active endpoint response: SQN: {MsgDataSQN} Status: {DisplayStatusCode(MsgDataStatus)} Short Addr: {MsgDataShAddr} List: {MsgDataEpCount} Ep List: {MsgDataEPlist}') + if len(MsgData) < 8: + self.log.logging('Pairing', 'Error', f'Decode8045 - received invalid payload {MsgData}') + return + MsgDataSQN, MsgDataStatus, MsgDataShAddr = ( MsgData[:2], MsgData[2:4], MsgData[4:8] ) + if MsgDataShAddr == '0000': + MsgDataEpCount, MsgDataEPlist = ( MsgData[8:10], MsgData[10:] ) receiveZigateEpList(self, MsgDataEpCount, MsgDataEPlist) return if not DeviceExist(self, Devices, MsgDataShAddr): self.log.logging('Input', 'Log', f'Decode8045 - KeyError: MsgDataShAddr = {MsgDataShAddr}') return - device = self.ListOfDevices[MsgDataShAddr] - if device['Status'] == 'inDB': return @@ -40,6 +39,13 @@ def Decode8045(self, Devices, MsgData, MsgLQI): updSQN(self, MsgDataShAddr, MsgDataSQN) updLQI(self, MsgDataShAddr, MsgLQI) + if len(MsgData) < 10: + self.log.logging('Pairing', 'Error', f'Decode8045 - received invalid payload from {MsgDataShAddr} {MsgData}') + return + MsgDataEpCount, MsgDataEPlist = ( MsgData[8:10], MsgData[10:] ) + + self.log.logging('Pairing', 'Debug', f'Decode8045 - Reception Active endpoint response: SQN: {MsgDataSQN} Status: {DisplayStatusCode(MsgDataStatus)} Short Addr: {MsgDataShAddr} List: {MsgDataEpCount} Ep List: {MsgDataEPlist}') + for i in range(0, 2 * int(MsgDataEpCount, 16), 2): tmpEp = MsgDataEPlist[i:i + 2] device['Ep'].setdefault(tmpEp, {}) From 7d4222523285f27086c171f6e3894819b88b6cab Mon Sep 17 00:00:00 2001 From: Pipiche <8291674+pipiche38@users.noreply.github.com> Date: Sat, 10 Feb 2024 13:23:15 +0100 Subject: [PATCH 11/21] Use Neighbour tables to complete the Topology report and also retreive the aqara devices (#1714) --- Classes/WebServer/rest_Topology.py | 43 +++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/Classes/WebServer/rest_Topology.py b/Classes/WebServer/rest_Topology.py index 5405ce460..557afa7b4 100644 --- a/Classes/WebServer/rest_Topology.py +++ b/Classes/WebServer/rest_Topology.py @@ -51,7 +51,7 @@ def rest_netTopologie(self, verb, data, parameters): if not os.path.isfile(_filename): _response["Data"] = json.dumps({}, sort_keys=True) - self.logging("Log", "Filename: %s not found !!" % _filename) + self.logging("Debug", "Filename: %s not found !!" % _filename) return _response # Read the file, as we have anyway to do it @@ -402,8 +402,11 @@ def collect_routing_table(self, time_stamp=None): _topo = [] self.logging( "Debug", "collect_routing_table - TimeStamp: %s" %time_stamp) for father in self.ListOfDevices: + self.logging( "Debug", f"check {father} child from routing table") for child in extract_routes(self, father, time_stamp): + self.logging( "Debug", f"Found child {child} from routes table") if child not in self.ListOfDevices: + self.logging( "Debug", f"Found child {child} from routes table but not found in ListOfDevices") continue _relation = { "Father": get_node_name( self, father), @@ -411,12 +414,15 @@ def collect_routing_table(self, time_stamp=None): "_lnkqty": get_lqi_from_neighbours(self, father, child), "DeviceType": find_device_type(self, child) } - self.logging( "Log", "Relationship - %15.15s (%s) - %15.15s (%s) %3s %s" % ( + self.logging( "Debug", "Relationship - %15.15s (%s) - %15.15s (%s) %3s %s" % ( _relation["Father"], father, _relation["Child"], child, _relation["_lnkqty"], _relation["DeviceType"]),) _topo.append( _relation ) - + + self.logging( "Debug", f"check {father} child from associated table") for child in collect_associated_devices( self, father, time_stamp): + self.logging( "Debug", f"Found child {child} from associated devices table") if child not in self.ListOfDevices: + self.logging( "Debug", f"Found child {child} but not found in ListOfDevices") continue _relation = { "Father": get_node_name( self, father), @@ -424,10 +430,32 @@ def collect_routing_table(self, time_stamp=None): "_lnkqty": get_lqi_from_neighbours(self, father, child), "DeviceType": find_device_type(self, child) } + self.logging( "Debug", "Relationship - %15.15s (%s) - %15.15s (%s) %3s %s" % ( _relation["Father"], father, _relation["Child"], child, _relation["_lnkqty"], _relation["DeviceType"]),) if _relation not in _topo: _topo.append( _relation ) + + self.logging( "Debug", f"check {father} child from Neigbours table") + for child in collect_neighbours_devices( self, father, time_stamp): + self.logging( "Debug", f"Found child {child} from associated devices table") + if child not in self.ListOfDevices: + self.logging( "Debug", f"Found child {child} but not found in ListOfDevices") + continue + _relation = { + "Father": get_node_name( self, father), + "Child": get_node_name( self, child), + "_lnkqty": get_lqi_from_neighbours(self, father, child), + "DeviceType": find_device_type(self, child) + } + + self.logging( "Debug", "Relationship - %15.15s (%s) - %15.15s (%s) %3s %s" % ( + _relation["Father"], father, _relation["Child"], child, _relation["_lnkqty"], _relation["DeviceType"]),) + if _relation not in _topo: + _topo.append( _relation ) + + self.logging( "Debug", f"check {father} child from Neigbours table") + return _topo @@ -435,7 +463,14 @@ def collect_associated_devices( self, node, time_stamp=None): last_associated_devices = get_device_table_entry(self, node, "AssociatedDevices", time_stamp) self.logging( "Debug", "collect_associated_devices %s -> %s" %(node, str(last_associated_devices))) return list(last_associated_devices) - + + +def collect_neighbours_devices( self, node, time_stamp=None): + last_neighbours_devices = get_device_table_entry(self, node, "Neighbours", time_stamp) + self.logging( "Debug", "collect_neighbours_devices %s -> %s" %(node, str(last_neighbours_devices))) + keys_with_child_relation = [key for item in last_neighbours_devices for key, value in item.items() if value.get('_relationshp') == 'Child'] + return list(keys_with_child_relation) + def extract_routes( self, node, time_stamp=None): node_routes = [] From f7eb17c7833c8ea64399eb9121d1c064be48406e Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sat, 10 Feb 2024 15:33:53 +0100 Subject: [PATCH 12/21] simplify code --- Classes/WebServer/rest_Topology.py | 44 ++---------------------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/Classes/WebServer/rest_Topology.py b/Classes/WebServer/rest_Topology.py index 557afa7b4..ed96f04ad 100644 --- a/Classes/WebServer/rest_Topology.py +++ b/Classes/WebServer/rest_Topology.py @@ -403,42 +403,8 @@ def collect_routing_table(self, time_stamp=None): self.logging( "Debug", "collect_routing_table - TimeStamp: %s" %time_stamp) for father in self.ListOfDevices: self.logging( "Debug", f"check {father} child from routing table") - for child in extract_routes(self, father, time_stamp): - self.logging( "Debug", f"Found child {child} from routes table") - if child not in self.ListOfDevices: - self.logging( "Debug", f"Found child {child} from routes table but not found in ListOfDevices") - continue - _relation = { - "Father": get_node_name( self, father), - "Child": get_node_name( self, child), - "_lnkqty": get_lqi_from_neighbours(self, father, child), - "DeviceType": find_device_type(self, child) - } - self.logging( "Debug", "Relationship - %15.15s (%s) - %15.15s (%s) %3s %s" % ( - _relation["Father"], father, _relation["Child"], child, _relation["_lnkqty"], _relation["DeviceType"]),) - _topo.append( _relation ) - - self.logging( "Debug", f"check {father} child from associated table") - for child in collect_associated_devices( self, father, time_stamp): - self.logging( "Debug", f"Found child {child} from associated devices table") - if child not in self.ListOfDevices: - self.logging( "Debug", f"Found child {child} but not found in ListOfDevices") - continue - _relation = { - "Father": get_node_name( self, father), - "Child": get_node_name( self, child), - "_lnkqty": get_lqi_from_neighbours(self, father, child), - "DeviceType": find_device_type(self, child) - } - - self.logging( "Debug", "Relationship - %15.15s (%s) - %15.15s (%s) %3s %s" % ( - _relation["Father"], father, _relation["Child"], child, _relation["_lnkqty"], _relation["DeviceType"]),) - if _relation not in _topo: - _topo.append( _relation ) - - self.logging( "Debug", f"check {father} child from Neigbours table") - for child in collect_neighbours_devices( self, father, time_stamp): - self.logging( "Debug", f"Found child {child} from associated devices table") + for child in set( extract_routes(self, father, time_stamp) + collect_associated_devices( self, father, time_stamp) + collect_neighbours_devices( self, father, time_stamp) ): + self.logging( "Debug", f"Found child {child}") if child not in self.ListOfDevices: self.logging( "Debug", f"Found child {child} but not found in ListOfDevices") continue @@ -448,13 +414,9 @@ def collect_routing_table(self, time_stamp=None): "_lnkqty": get_lqi_from_neighbours(self, father, child), "DeviceType": find_device_type(self, child) } - self.logging( "Debug", "Relationship - %15.15s (%s) - %15.15s (%s) %3s %s" % ( _relation["Father"], father, _relation["Child"], child, _relation["_lnkqty"], _relation["DeviceType"]),) - if _relation not in _topo: - _topo.append( _relation ) - - self.logging( "Debug", f"check {father} child from Neigbours table") + _topo.append( _relation ) return _topo From 1e5503c96bf5da1177a54f3de0330d029c9535da Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sun, 11 Feb 2024 11:38:58 +0100 Subject: [PATCH 13/21] remove old config files available on Certified Db --- Conf/Certified/Danfoss/eT093WRG.json | 92 ------------------- .../TS0601-SmartAirHouseKeeper.json | 38 -------- Conf/Local-Devices/lumi.curtain.acn002.json | 55 ----------- 3 files changed, 185 deletions(-) delete mode 100644 Conf/Certified/Danfoss/eT093WRG.json delete mode 100644 Conf/Local-Devices/TS0601-SmartAirHouseKeeper.json delete mode 100644 Conf/Local-Devices/lumi.curtain.acn002.json diff --git a/Conf/Certified/Danfoss/eT093WRG.json b/Conf/Certified/Danfoss/eT093WRG.json deleted file mode 100644 index 33af036e0..000000000 --- a/Conf/Certified/Danfoss/eT093WRG.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "_comment": "Popp by Danfoss ( https://assets.danfoss.com/documents/DOC235786405834/DOC235786405834.pdf )", - "_version": "0.1", - "Ep": { - "01": { - "0000": "", - "0001": "", - "0003": "", - "000a": "", - "0019": "", - "0020": "", - "0201": "", - "0b05": "", - "Type": "Temp/ThermoSetpoint/Valve/Door" - } - }, - "Type": "", - "ClusterToBind": [ - "0001", - "0019", - "0201" - ], - "ConfigureReporting": { - "0001": { "Attributes": { "0021": { "DataType": "20", "MinInterval": "0E10", "MaxInterval": "A8C0", "TimeOut": "0000", "Change": "02" } } }, - "0201": { - "Attributes": { - "4000": { "ManufSpecific": "1246", "DataType": "30", "MinInterval": "003C", "MaxInterval": "A8C0", "TimeOut": "0000", "Change": "00" }, - "4012": { "ManufSpecific": "1246","DataType": "10", "MinInterval": "0001", "MaxInterval": "0000", "TimeOut": "0000", "Change": "00" }, - "0000": { "DataType": "29", "MinInterval": "003C", "MaxInterval": "0E10", "TimeOut": "0000", "Change": "0050" }, - "0012": { "DataType": "29", "MinInterval": "0001", "MaxInterval": "A8C0", "TimeOut": "0000", "Change": "0001" }, - "0008": { "DataType": "20", "MinInterval": "012C", "MaxInterval": "A8C0", "TimeOut": "0000", "Change": "01" } - } - } - }, - "ReadAttributes": { - "0000": [ - "0000", - "0001", - "0002", - "0003", - "0004", - "0005", - "0006", - "0007", - "0010" - ], - "0001": [ - "0020" - ], - "0020": [], - "0201": [ - "0000", - "0003", - "0004", - "0008", - "0010", - "0012", - "0015", - "0016", - "001b", - "001c", - "4000", - "4010", - "4011", - "4014", - "4015", - "4020" - ], - "0204": [ - "0000", - "0001", - "4000" - ], - "0b05": [ - "011c", - "011d", - "4000" - ] - }, - "PollingEnabled": 1, - "BatteryPercentageConverter": 2, - "Param": { - "Calibration": 0, - "eTRVExerciseDay": 0, - "eTRVExerciseTime": 720, - "DanfossRoom": 0, - "DanfossRoomFreq": 0, - "DanfossSetPointType": 0, - "DanfossTRVOrientation": "H", - "DanfossViewDirection": 1 - } -} diff --git a/Conf/Local-Devices/TS0601-SmartAirHouseKeeper.json b/Conf/Local-Devices/TS0601-SmartAirHouseKeeper.json deleted file mode 100644 index 4818fd37e..000000000 --- a/Conf/Local-Devices/TS0601-SmartAirHouseKeeper.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "_comment": "Tuya Smart Air Box X1", - "_version": "0.10", - "Identifier": [ - [ "TS0601", "_TZE200_dwcarsat" ] - ], - "Ep": { - "01": { - "0000": "", - "0004": "", - "0005": "", - "ef00": "", - "000a": "", - "0019": "", - "Type": "Temp/Humi/CarbonDioxyde/CH2O/Voc/PM25" - } - }, - "Type": "", - "ClusterToBind": [ - "0000", - "ef00" - ], - "ConfigureReporting": {}, - "ReadAttributes": { - "0000": [ "0004", "0000", "0001", "0005", "0007", "fffe" ] - }, - "TS0601_DP": { - "02": { "sensor_type": "mp25", "EvalExp": "int(value/10)" }, - "12": { "sensor_type": "temperature", "EvalExp": "int(value/10)" }, - "13": { "sensor_type": "humidity", "EvalExp": "int(value/10)" }, - "14": { "sensor_type": "ch20", "EvalExp": "int(value/10)" }, - "15": { "sensor_type": "voc", "EvalExp": "int(value/10)" }, - "16": { "sensor_type": "co2", "EvalExp": "int(value/10)" } - }, - "Param": { - "AcquisitionFrequency": 60 - } -} diff --git a/Conf/Local-Devices/lumi.curtain.acn002.json b/Conf/Local-Devices/lumi.curtain.acn002.json deleted file mode 100644 index 9a28a2c6c..000000000 --- a/Conf/Local-Devices/lumi.curtain.acn002.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "_source": "https://github.com/zigbeefordomoticz/Domoticz-Zigbee/issues/1607", - "_blakadder": "https://zigbee.blakadder.com/Aqara_ZNJLBL01LM.html ", - "Ep": { - "01": { - "0000": { - "Attributes": { - "fff0": { "Enabled": true, "Name": "Aqara_0000_fff0", "DataType": "42" , "ManufRawData": true, "ManufSpecificFunc": "Lumi_fcc0", "ActionList": [ "check_store_value"]}, - "ff01": { "Enabled": true, "Name": "Aqara_0000_ff01", "DataType": "42" , "ManufRawData": true, "ManufSpecificFunc": "Lumi_fcc0", "ActionList": [ "check_store_value"]}, - "ff02": { "Enabled": true, "Name": "Aqara_0000_ff02", "DataType": "4c" , "ManufRawData": true, "ManufSpecificFunc": "Lumi_fcc0", "ActionList": [ "check_store_value"]} - } - }, - "0002": "", - "0003": "", - "0004": "", - "0005": "", - "0006": "", - "0009": "", - "000a": "", - "000d": { - "Attributes": { - "0055": { - "Enabled": true, - "Name": "CurtainPosition", - "DataType": "39" , - "Acc": "RW" , - "Mandatory": false, - "EvalExp": "(100 - value)", - "UpdDomoDeviceWithCluster": "0102", - "UpdDomoDeviceWithAttribute": "0008", - "ActionList": [ "check_store_value", "upd_domo_device"] } - } - }, - "0013": "", - "0019": "", - "0102": "", - "Type": "Temp/Curtain" - } - }, - "Type": "", - "ClusterToBind": [ ], - "ConfigureReporting": { - }, - "ReadAttributes": { - "0000": [ "0000", "0001", "0002", "0003", "0004", "0005", "0006", "0007", "000a", "4000"], - "0006": [], - "000d": [ "0055" ], - "0013": [], - "0019": [], - "0102": [ "0000", "0007", "0008" ] - - }, - "BatteryDevice": 1, - "BatteryPoweredDevice": 1 -} From 73c9ddb92f4692459520a32eb293d8f80d99bd49 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sun, 11 Feb 2024 11:46:07 +0100 Subject: [PATCH 14/21] remove old config files available on Certified Db --- Conf/Local-Devices/AQSZB-110.json | 45 ------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 Conf/Local-Devices/AQSZB-110.json diff --git a/Conf/Local-Devices/AQSZB-110.json b/Conf/Local-Devices/AQSZB-110.json deleted file mode 100644 index f41af0722..000000000 --- a/Conf/Local-Devices/AQSZB-110.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "_source": "https://github.com/zigbeefordomoticz/Domoticz-Zigbee/issues/1448", - "_blakadder": "https://zigbee.blakadder.com/Aqara_AAQS-S01.html", - "Identifier" : [ - ["AQSZB-110", "Develco Products A/S"] - ], - "Ep": { - "0000": "", - "0001": "", - "0003": "", - "000a": "", - "0019": "", - "0020": "", - "0402": "", - "0405": "", - "fc03": { "Attributes": { - "0000": { - "Name": "VOC", - "DataType": "21", - "UpdDomoDeviceWithCluster": "000c", - "ActionList": [ "check_store_value", "upd_domo_device" ] - }}}, - "Type": "Temp/Humi/Voc" - }, - "Type": "", - "ClusterToBind": [ "0001", "0402", "0405", "fc03" ], - "ConfigureReporting": { - "0001": { "Attributes": { "0020": { "DataType": "20", "MinInterval": "012C", "MaxInterval": "A8C0", "TimeOut": "0000", "Change": "01" }}}, - "0402": { "Attributes": { "0000": { "DataType": "29", "MinInterval": "000a", "MaxInterval": "012c", "TimeOut": "0000", "Change": "0001" }}}, - "0405": { "Attributes": { "0000": { "DataType": "21", "MinInterval": "000a", "MaxInterval": "012c", "TimeOut": "0000", "Change": "0001" }}}, - "fc03": { "Attributes": { "0000": { "ManufSpecific": "1015", "DataType": "21", "MinInterval": "000a", "MaxInterval": "012c", "TimeOut": "0000", "Change": "0001" }}} - }, - "ReadAttributes": { - "0000": [ "0000", "0004", "0005", "0006", "0007" ], - "0001": [ "0020" ], - "0402": [ "0000", "0001", "0002" ], - "0405": [ "0000", "0001", "0002", "0003" ], - "fc03": [ ], - "0019": [] - }, - "BatteryDevice": 1, - "VoltageConverter": 10, - "Param": { - } -} From ef0bf6249f7b2aa0012a6b7c9e5807866fd3e325 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Thu, 15 Feb 2024 19:18:23 +0100 Subject: [PATCH 15/21] Parameters to set the IAS sensitivity --- Modules/ias_settings.py | 57 +++++++++++++++++++++++++++++++++++++++++ Modules/paramDevice.py | 2 ++ 2 files changed, 59 insertions(+) create mode 100644 Modules/ias_settings.py diff --git a/Modules/ias_settings.py b/Modules/ias_settings.py new file mode 100644 index 000000000..76fa4add9 --- /dev/null +++ b/Modules/ias_settings.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Implementation of Zigbee for Domoticz plugin. +# +# This file is part of Zigbee for Domoticz plugin. https://github.com/zigbeefordomoticz/Domoticz-Zigbee +# (C) 2015-2024 +# +# Initial authors: zaraki673 & pipiche38 +# +# SPDX-License-Identifier: GPL-3.0 license + +from Modules.basicOutputs import write_attribute +from Modules.enki import enki_set_poweron_after_offon_device, is_enky_device +from Modules.philips import is_philips_device +from Modules.readAttributes import ReadAttributeRequest_0006_400x +from Modules.tools import get_deviceconf_parameter_value, getListOfEpForCluster +from Modules.tuya import (get_tuya_attribute, is_tuya_switch_relay, + tuya_switch_relay_status) +from Modules.zigateConsts import ZIGATE_EP + +IAS_CLUSTER_ID = "0500" + +ONOFF_CONFIG_SET = { + "IAS_CIE_Address": ( "0010", "f0"), + "ZoneID": ( "0011", "20"), + "NumberOfZoneSensitivityLevelsSupported": ( "0012", "20"), + "CurrentZoneSensitivityLevel": ( "0013", "20") +} + + +def ias_CurrentZoneSensitivityLevel(self, nwkid, ep, value): + """ Allows an IAS Zone client to query and configure the IAS Zone server’s sensitivity level. """ + + # The default value 0x00 is the device’s default sensitivity level as configured by the manufacturer. It MAY + # correspond to the same sensitivity as another value in the NumberOfZoneSensitivityLevelsSupported, but this + # is the default sensitivity to be used if the CurrentZoneSensitivityLevel attribute is not otherwise configured + # by an IAS Zone client. + + self.log.logging( "onoffSettings", "Debug", f"ias_CurrentZoneSensitivityLevel for {nwkid}/{ep} - value: {value}", nwkid ) + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + IAS_CLUSTER_ID, + "0000", + "00", + ONOFF_CONFIG_SET[ "CurrentZoneSensitivityLevel"][0], + ONOFF_CONFIG_SET[ "CurrentZoneSensitivityLevel"][1], + "%02x" %value, + ackIsDisabled=False, ) + + +IAS_DEVICE_PARAMETERS = { + "CurrentZoneSensitivityLevel": ias_CurrentZoneSensitivityLevel +} diff --git a/Modules/paramDevice.py b/Modules/paramDevice.py index c48c91591..f5a91f05b 100644 --- a/Modules/paramDevice.py +++ b/Modules/paramDevice.py @@ -23,6 +23,7 @@ from Modules.danfoss import (danfoss_covered, danfoss_exercise_day_of_week, danfoss_exercise_trigger_time, danfoss_orientation, danfoss_viewdirection) +from Modules.ias_settings import IAS_DEVICE_PARAMETERS from Modules.legrand_netatmo import (legrand_Dimmer_by_nwkid, legrand_enable_Led_IfOn_by_nwkid, legrand_enable_Led_InDark_by_nwkid, @@ -85,6 +86,7 @@ def sanity_check_of_param(self, NwkId): # Load specific settings DEVICE_PARAMETERS.update(ONOFF_DEVICE_PARAMETERS) DEVICE_PARAMETERS.update(OCCUPANCY_DEVICE_PARAMETERS) + DEVICE_PARAMETERS.update(IAS_DEVICE_PARAMETERS) # Load Manufacturer specific settings DEVICE_PARAMETERS.update(SONOFF_DEVICE_PARAMETERS) From 7db66bec01fe9c31db1bb7a52fae65dbf3a0dce0 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sat, 17 Feb 2024 09:11:15 +0100 Subject: [PATCH 16/21] Make sure that the onoff_startup is an int and not str --- Modules/onoff_settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/onoff_settings.py b/Modules/onoff_settings.py index e10162139..817017152 100644 --- a/Modules/onoff_settings.py +++ b/Modules/onoff_settings.py @@ -66,6 +66,11 @@ def onoff_startup_onoff_mode(self, nwkid, ep, value): self.log.logging( "onoffSettings", "Debug", f"onoff_startup_onoff_mode for {nwkid}/{ep} - value: {value}", nwkid ) + if isinstance(value, str): + old_value = value + value = int(value) + self.log.logging( "onoffSettings", "Log", f"onoff_startup_onoff_mode for {nwkid}/{ep} - value: {old_value} converted to {value}", nwkid ) + write_attribute( self, nwkid, From 58277edb7e5ec304bd43efefc16747107e482c66 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sat, 17 Feb 2024 09:41:19 +0100 Subject: [PATCH 17/21] cosmetic, sort the SIMPLE_WIDGET dict --- Modules/domoCreate.py | 434 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 350 insertions(+), 84 deletions(-) diff --git a/Modules/domoCreate.py b/Modules/domoCreate.py index b99723a51..87d19a2f1 100644 --- a/Modules/domoCreate.py +++ b/Modules/domoCreate.py @@ -112,8 +112,8 @@ def createSwitchSelector(self, nbSelector, DeviceType=None, OffHidden=False, Sel Options["LevelNames"] += "BT %03s | " % bt Options["LevelActions"] += "|" - Options["LevelNames"] = Options["LevelNames"][:-2] # Remove the last '| ' - Options["LevelActions"] = Options["LevelActions"][:-1] # Remove the last '|' + Options["LevelNames"] = Options["LevelNames"][:-2] # Remove the last "| " + Options["LevelActions"] = Options["LevelActions"][:-1] # Remove the last "|" if SelectorStyle: Options["SelectorStyle"] = "%s" % SelectorStyle @@ -160,7 +160,7 @@ def over_write_type_from_deviceconf( self, Devices, NwkId): if NwkId not in self.ListOfDevices: self.log.logging( "WidgetCreation", "Log", "over_write_type_from_deviceconf - NwkId : %s not found " % NwkId, NwkId ) return - if 'Ep' not in self.ListOfDevices[ NwkId ]: + if "Ep" not in self.ListOfDevices[ NwkId ]: self.log.logging( "WidgetCreation", "Log", "over_write_type_from_deviceconf - NwkId : %s 'Ep' not found" % NwkId, NwkId ) return @@ -173,23 +173,23 @@ def over_write_type_from_deviceconf( self, Devices, NwkId): return _deviceConf = self.DeviceConf[ _model ] - for _ep in self.ListOfDevices[ NwkId ]['Ep']: - if _ep not in _deviceConf['Ep']: + for _ep in self.ListOfDevices[ NwkId ]["Ep"]: + if _ep not in _deviceConf["Ep"]: self.log.logging( "WidgetCreation", "Log", "over_write_type_from_deviceconf - NwkId : %s 'ep: %s' not found in DeviceConf" % (NwkId, _ep), NwkId ) continue - if "Type" not in _deviceConf['Ep'][ _ep ]: + if "Type" not in _deviceConf["Ep"][ _ep ]: self.log.logging( "WidgetCreation", "Log", "over_write_type_from_deviceconf - NwkId : %s 'Type' not found in DeviceConf" % (NwkId,), NwkId ) continue - if "Type" in self.ListOfDevices[ NwkId ]['Ep'][ _ep ] and self.ListOfDevices[ NwkId ]['Ep'][ _ep ]["Type"] == _deviceConf['Ep'][ _ep ]["Type"]: + if "Type" in self.ListOfDevices[ NwkId ]["Ep"][ _ep ] and self.ListOfDevices[ NwkId ]["Ep"][ _ep ]["Type"] == _deviceConf["Ep"][ _ep ]["Type"]: self.log.logging( "WidgetCreation", "Debug", "over_write_type_from_deviceconf - NwkId : %s Device Type: %s == Device Conf Type: %s" % ( - NwkId,self.ListOfDevices[ NwkId ]['Ep'][ _ep ]["Type"] , _deviceConf['Ep'][ _ep ]["Type"]), NwkId ) + NwkId,self.ListOfDevices[ NwkId ]["Ep"][ _ep ]["Type"] , _deviceConf["Ep"][ _ep ]["Type"]), NwkId ) continue self.log.logging( "WidgetCreation", "Debug", "over_write_type_from_deviceconf - Ep Overwrite Type with a new one %s on ep: %s" % ( - _deviceConf['Ep'][ _ep ]["Type"], _ep), NwkId ) + _deviceConf["Ep"][ _ep ]["Type"], _ep), NwkId ) - self.ListOfDevices[ NwkId ]['Ep'][ _ep ]["Type"] = _deviceConf['Ep'][ _ep ]["Type"] + self.ListOfDevices[ NwkId ]["Ep"][ _ep ]["Type"] = _deviceConf["Ep"][ _ep ]["Type"] def extract_key_infos( self, NWKID, Ep, GlobalEP, GlobalType): @@ -260,7 +260,7 @@ def CreateDomoDevice(self, Devices, NWKID): self.log.logging( "WidgetCreation", "Debug", "CreateDomoDevice - Ep to be processed : %s " % self.ListOfDevices[NWKID]["Ep"].keys(), NWKID ) for Ep in self.ListOfDevices[NWKID]["Ep"]: - # Use 'type' at level EndPoint if existe + # Use "type" at level EndPoint if existe self.log.logging("WidgetCreation", "Debug", "CreateDomoDevice - Process EP : %s GlobalEP: %s GlobalType: %s" %( Ep, GlobalEP, str(GlobalType)), NWKID) @@ -278,7 +278,7 @@ def CreateDomoDevice(self, Devices, NWKID): # In case Type is issued from GetType functions, this is based on Clusters, # In such case and the device is a Bulb or a Dimmer Switch we will get a combinaison of Switch/LvlControl and ColorControlxxx # We want to avoid creating of 3 widgets while 1 is enought. - # if self.ListOfDevices[NWKID][ 'Model'] not in self.DeviceConf: + # if self.ListOfDevices[NWKID][ "Model"] not in self.DeviceConf: self.log.logging("WidgetCreation", "Debug", "---> Check if we need to reduce Type: %s" % Type) Type = cleanup_widget_Type(Type) @@ -334,7 +334,7 @@ def update_widget_type_if_possible( self, Nwkid, widget_type): elif self.ListOfDevices[Nwkid]["ZDeviceID"] == "0200": return "VenetianInverted" - if ( widget_type == "LvlControl" and self.ListOfDevices[Nwkid]["Model"] in ('', {}) and self.ListOfDevices[Nwkid]["ProfileID"] == "0104" ): + if ( widget_type == "LvlControl" and self.ListOfDevices[Nwkid]["Model"] in ("", {}) and self.ListOfDevices[Nwkid]["ProfileID"] == "0104" ): if self.ListOfDevices[Nwkid]["ZDeviceID"] == "0202": # Windows Covering / Profalux -> Inverted return "BlindInverted" @@ -385,7 +385,7 @@ def number_switch_selectors( widget_type ): if "LevelNames" not in SWITCH_SELECTORS[ widget_type ]: return 0 levels = SWITCH_SELECTORS[ widget_type ]["LevelNames"] - return len( levels.split('|') ) + return len( levels.split("|") ) def off_hidden( widget_type ): @@ -522,79 +522,345 @@ def set_default_value( self, Devices, unit, widget_record): nValue = widget_record["nValue"] UpdateDevice_v2(self, Devices, unit, nValue, sValue, 0, 0, ForceUpdate_=True) - SIMPLE_WIDGET = { - "Temp+Hum+Baro": { "widgetType": "Temp+Hum+Baro", }, - "Temp+Hum": { "widgetType": "Temp+Hum", }, - "Temp": { "widgetType": "Temperature", }, - "Humi": { "widgetType": "Humidity", }, - "Baro": { "widgetType": "Barometer", }, - "AirQuality": { "widgetType": "Air Quality", }, - "Power": { "widgetType": "Usage", }, - "Meter": { "widgetType": "kWh", }, - "ConsoMeter": { "Type": 113, "Subtype": 0, "Switchtype": 0, "sValue": "0", "nValue": 0}, - "ProdMeter": { "Type": 113, "Subtype": 0, "Switchtype": 4, "sValue": "0", "nValue": 0}, - "ProdPower": { "widgetType": "Usage", }, - "Voltage": { "widgetType": "Voltage", }, - "Voc": { "widgetType": "Custom", "Options": "1;ppm" }, - "PM25": { "widgetType": "Custom", "Options": "1;ppm" }, - "CH2O": { "widgetType": "Custom", "Options": "1;ppm" }, - "SmokePPM": { "widgetType": "Custom", "Options": "1;ppm" }, - "CarbonDioxyde": { "Type": 0xF3, "Subtype": 31, "Switchtype": 0, "Options": "1;ppm", }, - "CarbonMonoxyde": { "Type": 0xF3, "Subtype": 31, "Switchtype": 0, "Options": "1;ppm", }, - "Analog": { "Type": 0xF3, "Subtype": 31, "Switchtype": 0, "Options": "1;tbd", }, - "Alarm": { "Type": 243, "Subtype": 22, "Switchtype": 0, }, - "Tamper": { "Type": 243, "Subtype": 22, "Switchtype": 0, }, - "Alarm_ZL": { "Type": 243, "Subtype": 22, "Switchtype": 0, }, - "Alarm_ZL2": { "Type": 243, "Subtype": 22, "Switchtype": 0, }, - "Alarm_ZL3": { "Type": 243, "Subtype": 22, "Switchtype": 0, }, - "AirPurifierAlarm": { "Type": 243, "Subtype": 22, "Switchtype": 0, }, - "PowerFactor": { "Type": 243, "Subtype": 6, "Switchtype": 0, }, - "Valve": { "Type": 243, "Subtype": 6, "Switchtype": 0, }, - "FanSpeed": { "Type": 243, "Subtype": 6, "Switchtype": 0, }, - "ThermoSetpoint": { "Type": 242, "Subtype": 1, }, - "TempSetCurrent": { "Type": 242, "Subtype": 1, }, - "Ampere": { "Type": 243, "Subtype": 23, }, - "Ampere3": { "Type": 89, "Subtype": 1, }, - "Door": { "Type": 244, "Subtype": 73, "Switchtype": 11, }, - "DoorSensor": { "Type": 244, "Subtype": 73, "Switchtype": 11, }, - "DoorLock": { "Type": 244, "Subtype": 73, "Switchtype": 19, }, - "TuyaDoorLock": { "Type": 244, "Subtype": 73, "Switchtype": 19, }, - "Motion": { "Type": 244, "Subtype": 73, "Switchtype": 8, }, - "LivoloSWL": { "Type": 244, "Subtype": 73, "Switchtype": 0, }, - "LivoloSWR": { "Type": 244, "Subtype": 73, "Switchtype": 0, }, - "Smoke": { "Type": 244, "Subtype": 73, "Switchtype": 5, }, - "Lux": { "Type": 246, "Subtype": 1, "Switchtype": 0, }, - "Switch": { "Type": 244, "Subtype": 73, "Switchtype": 0, }, - "Plug": { "Type": 244, "Subtype": 73, "Switchtype": 0, "Image": 1, }, - "SwitchButton": { "Type": 244, "Subtype": 73, "Switchtype": 0, }, - "PAC-SWITCH": { "Type": 244, "Subtype": 73, "Switchtype": 0, }, - "ShutterCalibration": { "Type": 244, "Subtype": 73, "Switchtype": 0, }, - "HeatingStatus": { "Type": 244, "Subtype": 73, "Switchtype": 0, "Image": 15, }, - "ThermoOnOff": { "Type": 244, "Subtype": 73, "Switchtype": 0, "Image": 15, }, - "HeatingSwitch": { "Type": 244, "Subtype": 73, "Switchtype": 0, "Image": 15, }, - "Button": { "Type": 244, "Subtype": 73, "Switchtype": 9, }, - "Strength": { "Type": 243, "Subtype": 31, }, - "Orientation": { "Type": 243, "Subtype": 19, }, - "Water": { "Type": 244, "Subtype": 73, "Switchtype": 0, "Image": 11, }, - "P1Meter": { "Type": 250, "Subtype": 1, "Switchtype": 1, }, - "P1Meter_ZL": { "Type": 250, "Subtype": 1, "Switchtype": 1, }, - "ColorControlRGBWW": { "Type": 241, "Subtype": 0x04, "Switchtype": 7, }, - "ColorControlFull": { "Type": 241, "Subtype": 0x07, "Switchtype": 7, }, - "ColorControlWW": { "Type": 241, "Subtype": 0x08, "Switchtype": 7, }, - "ColorControlRGBW": { "Type": 241, "Subtype": 0x01, "Switchtype": 7, }, - "ColorControlRGBWZ": { "Type": 241, "Subtype": 0x02, "Switchtype": 7, }, - "ColorControlRGB": { "Type": 241, "Subtype": 1, "Switchtype": 7, }, - "LvlControl": { "Type": 244, "Subtype": 73, "Switchtype": 7 }, - "SwitchAlarm": { "Type": 244, "Subtype": 73, "Switchtype": 0, "Image": 13 }, - "TamperSwitch": { "Type": 244, "Subtype": 73, "Switchtype": 0, }, - "Distance": { "Type": 243, "Subtype": 27, "Switchtype": 0}, - "WaterCounter": { "Type": 243, "Subtype": 28, "Switchtype": 2, "Image": 22, "sValue": "0", "nValue": 0}, - "GazMeter": { "Type": 251, "Subtype": 2, "Switchtype": 0, "sValue": "0", "nValue": 0}, - "Counter": { "Type": 113, "Subtype": 0, "Switchtype": 0, "sValue": "0", "nValue": 0}, - "Notification": {"Type":243, "Subtype":19, "Switchtype":0,} + "AirPurifierAlarm": { + "Type": 243, + "Subtype": 22, + "Switchtype": 0 + }, + "AirQuality": { + "widgetType": "Air Quality" + }, + "Alarm": { + "Type": 243, + "Subtype": 22, + "Switchtype": 0 + }, + "Alarm_ZL": { + "Type": 243, + "Subtype": 22, + "Switchtype": 0 + }, + "Alarm_ZL2": { + "Type": 243, + "Subtype": 22, + "Switchtype": 0 + }, + "Alarm_ZL3": { + "Type": 243, + "Subtype": 22, + "Switchtype": 0 + }, + "Ampere": { + "Type": 243, + "Subtype": 23 + }, + "Ampere3": { + "Type": 89, + "Subtype": 1 + }, + "Analog": { + "Type": 243, + "Subtype": 31, + "Switchtype": 0, + "Options": "1;tbd" + }, + "Baro": { + "widgetType": "Barometer" + }, + "Button": { + "Type": 244, + "Subtype": 73, + "Switchtype": 9 + }, + "CH2O": { + "widgetType": "Custom", + "Options": "1;ppm" + }, + "CarbonDioxyde": { + "Type": 243, + "Subtype": 31, + "Switchtype": 0, + "Options": "1;ppm" + }, + "CarbonMonoxyde": { + "Type": 243, + "Subtype": 31, + "Switchtype": 0, + "Options": "1;ppm" + }, + "ColorControlFull": { + "Type": 241, + "Subtype": 7, + "Switchtype": 7 + }, + "ColorControlRGB": { + "Type": 241, + "Subtype": 1, + "Switchtype": 7 + }, + "ColorControlRGBW": { + "Type": 241, + "Subtype": 1, + "Switchtype": 7 + }, + "ColorControlRGBWW": { + "Type": 241, + "Subtype": 4, + "Switchtype": 7 + }, + "ColorControlRGBWZ": { + "Type": 241, + "Subtype": 2, + "Switchtype": 7 + }, + "ColorControlWW": { + "Type": 241, + "Subtype": 8, + "Switchtype": 7 + }, + "ConsoMeter": { + "Type": 113, + "Subtype": 0, + "Switchtype": 0, + "sValue": "0", + "nValue": 0 + }, + "Counter": { + "Type": 113, + "Subtype": 0, + "Switchtype": 0, + "sValue": "0", + "nValue": 0 + }, + "Distance": { + "Type": 243, + "Subtype": 27, + "Switchtype": 0 + }, + "Door": { + "Type": 244, + "Subtype": 73, + "Switchtype": 11 + }, + "DoorLock": { + "Type": 244, + "Subtype": 73, + "Switchtype": 19 + }, + "DoorSensor": { + "Type": 244, + "Subtype": 73, + "Switchtype": 11 + }, + "FanSpeed": { + "Type": 243, + "Subtype": 6, + "Switchtype": 0 + }, + "GazMeter": { + "Type": 251, + "Subtype": 2, + "Switchtype": 0, + "sValue": "0", + "nValue": 0 + }, + "HeatingStatus": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0, + "Image": 15 + }, + "HeatingSwitch": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0, + "Image": 15 + }, + "Humi": { + "widgetType": "Humidity" + }, + "LivoloSWL": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0 + }, + "LivoloSWR": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0 + }, + "Lux": { + "Type": 246, + "Subtype": 1, + "Switchtype": 0 + }, + "LvlControl": { + "Type": 244, + "Subtype": 73, + "Switchtype": 7 + }, + "Meter": { + "widgetType": "kWh" + }, + "Motion": { + "Type": 244, + "Subtype": 73, + "Switchtype": 8 + }, + "Notification": { + "Type": 243, + "Subtype": 19, + "Switchtype": 0 + }, + "Orientation": { + "Type": 243, + "Subtype": 19 + }, + "P1Meter": { + "Type": 250, + "Subtype": 1, + "Switchtype": 1 + }, + "P1Meter_ZL": { + "Type": 250, + "Subtype": 1, + "Switchtype": 1 + }, + "PAC-SWITCH": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0 + }, + "PM25": { + "widgetType": "Custom", + "Options": "1;ppm" + }, + "Plug": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0, + "Image": 1 + }, + "Power": { + "widgetType": "Usage" + }, + "PowerFactor": { + "Type": 243, + "Subtype": 6, + "Switchtype": 0 + }, + "ProdMeter": { + "Type": 113, + "Subtype": 0, + "Switchtype": 4, + "sValue": "0", + "nValue": 0 + }, + "ProdPower": { + "widgetType": "Usage" + }, + "ShutterCalibration": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0 + }, + "Smoke": { + "Type": 244, + "Subtype": 73, + "Switchtype": 5 + }, + "SmokePPM": { + "widgetType": "Custom", + "Options": "1;ppm" + }, + "Strength": { + "Type": 243, + "Subtype": 31 + }, + "Switch": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0 + }, + "SwitchAlarm": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0, + "Image": 13 + }, + "SwitchButton": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0 + }, + "Tamper": { + "Type": 243, + "Subtype": 22, + "Switchtype": 0 + }, + "TamperSwitch": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0 + }, + "Temp": { + "widgetType": "Temperature" + }, + "Temp+Hum": { + "widgetType": "Temp+Hum" + }, + "Temp+Hum+Baro": { + "widgetType": "Temp+Hum+Baro" + }, + "TempSetCurrent": { + "Type": 242, + "Subtype": 1 + }, + "ThermoOnOff": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0, + "Image": 15 + }, + "ThermoSetpoint": { + "Type": 242, + "Subtype": 1 + }, + "TuyaDoorLock": { + "Type": 244, + "Subtype": 73, + "Switchtype": 19 + }, + "Valve": { + "Type": 243, + "Subtype": 6, + "Switchtype": 0 + }, + "Voc": { + "widgetType": "Custom", + "Options": "1;ppm" + }, + "Voltage": { + "widgetType": "Voltage" + }, + "Water": { + "Type": 244, + "Subtype": 73, + "Switchtype": 0, + "Image": 11 + }, + "WaterCounter": { + "Type": 243, + "Subtype": 28, + "Switchtype": 2, + "Image": 22, + "sValue": "0", + "nValue": 0 + } } + BLIND_DOMOTICZ_2022 = { # Blind old version before Domoticz 2023.1 "Blind": { "Type": 244, "Subtype": 73, "Switchtype": 13, "ForceClusterType": "LvlControl", }, From f2b18f00757fb753f9d57150cf72b909c3de82e5 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sat, 17 Feb 2024 13:02:13 +0100 Subject: [PATCH 18/21] Move enki setpower after onoff to onoff_settings --- Modules/enki.py | 43 ++++++++------------------------------- Modules/ias_settings.py | 6 ------ Modules/onoff_settings.py | 7 +++---- Modules/paramDevice.py | 6 ------ 4 files changed, 12 insertions(+), 50 deletions(-) diff --git a/Modules/enki.py b/Modules/enki.py index 780c5e6ee..b38a0c8ff 100644 --- a/Modules/enki.py +++ b/Modules/enki.py @@ -1,8 +1,15 @@ #!/usr/bin/env python3 -# coding: utf-8 -*- +# -*- coding: utf-8 -*- # -# Author: zaraki673 & pipiche38 +# Implementation of Zigbee for Domoticz plugin. # +# This file is part of Zigbee for Domoticz plugin. https://github.com/zigbeefordomoticz/Domoticz-Zigbee +# (C) 2015-2024 +# +# Initial authors: zaraki673 & pipiche38 +# +# SPDX-License-Identifier: GPL-3.0 license + from Modules.basicOutputs import set_poweron_afteroffon from Modules.readAttributes import ReadAttributeRequest_0006_400x @@ -10,35 +17,3 @@ def is_enky_device(self, nwkid): return self.ListOfDevices[nwkid]["Manufacturer"] == "1277" - - -def enki_set_poweron_after_offon(self, mode): - # call from WebServer - - if mode not in ENKI_POWERON_MODE: - self.log.logging("Enki", "Error", "enki_set_poweron_after_offon - Unknown mode: %s" % mode) - - for nwkid in self.ListOfDevices: - enki_set_poweron_after_offon_device(self, mode, nwkid) - - -def enki_set_poweron_after_offon_device(self, mode, nwkid): - if "Manufacturer" not in self.ListOfDevices[nwkid]: - return - if self.ListOfDevices[nwkid]["Manufacturer"] != "1277": - return - # We have a Enki device - if "01" not in self.ListOfDevices[nwkid]["Ep"]: - return - if "0006" not in self.ListOfDevices[nwkid]["Ep"]["01"]: - return - if "4003" not in self.ListOfDevices[nwkid]["Ep"]["01"]["0006"]: - self.log.logging("Enki", "Debug", "enki_set_poweron_after_offon Device: %s do not have a Set Power Attribute !" % nwkid) - ReadAttributeRequest_0006_400x(self, nwkid) - return - - # At that stage, we have a Philips device with Cluster 0006 and the right attribute - self.log.logging("Enki", "Debug", "enki_set_poweron_after_offon - Set PowerOn after OffOn of %s to %s" % ( - nwkid, ENKI_POWERON_MODE[mode]) ) - set_poweron_afteroffon(self, nwkid, OnOffMode=mode) - ReadAttributeRequest_0006_400x(self, nwkid) diff --git a/Modules/ias_settings.py b/Modules/ias_settings.py index 76fa4add9..7dd365d59 100644 --- a/Modules/ias_settings.py +++ b/Modules/ias_settings.py @@ -11,12 +11,6 @@ # SPDX-License-Identifier: GPL-3.0 license from Modules.basicOutputs import write_attribute -from Modules.enki import enki_set_poweron_after_offon_device, is_enky_device -from Modules.philips import is_philips_device -from Modules.readAttributes import ReadAttributeRequest_0006_400x -from Modules.tools import get_deviceconf_parameter_value, getListOfEpForCluster -from Modules.tuya import (get_tuya_attribute, is_tuya_switch_relay, - tuya_switch_relay_status) from Modules.zigateConsts import ZIGATE_EP IAS_CLUSTER_ID = "0500" diff --git a/Modules/onoff_settings.py b/Modules/onoff_settings.py index 817017152..952935695 100644 --- a/Modules/onoff_settings.py +++ b/Modules/onoff_settings.py @@ -11,7 +11,7 @@ # SPDX-License-Identifier: GPL-3.0 license from Modules.basicOutputs import write_attribute -from Modules.enki import enki_set_poweron_after_offon_device, is_enky_device +from Modules.enki import is_enky_device from Modules.philips import is_philips_device from Modules.readAttributes import ReadAttributeRequest_0006_400x from Modules.tools import get_deviceconf_parameter_value, getListOfEpForCluster @@ -135,12 +135,11 @@ def common_onoff_startup_onoff_mode(self, nwkid, mode): ListOfEp = ["0b",] elif is_enky_device(self, nwkid): # Enki Leroy Merlin - enki_set_poweron_after_offon_device(self, mode, nwkid) - return + ListOfEp = ["01",] elif is_tuya_switch_relay(self, nwkid): if get_tuya_attribute(self, nwkid, "RelayStatus") != mode: - tuya_switch_relay_status(self, nwkid, mode) + tuya_switch_relay_status(self, nwkid, status=mode) return else: diff --git a/Modules/paramDevice.py b/Modules/paramDevice.py index f5a91f05b..f9bd5cb85 100644 --- a/Modules/paramDevice.py +++ b/Modules/paramDevice.py @@ -10,12 +10,6 @@ # # SPDX-License-Identifier: GPL-3.0 license -""" - Module: paramDevice.py - - Description: implement the parameter device specific - -""" from DevicesModules.custom_sonoff import SONOFF_DEVICE_PARAMETERS from Modules.basicOutputs import (ballast_Configuration_max_level, From d0e48abed36c118f4e7b1d76e17191c2f983219d Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sat, 17 Feb 2024 13:11:51 +0100 Subject: [PATCH 19/21] resilience on handling the onoff_poweron settings --- Modules/onoff_settings.py | 13 +++++++++---- Modules/tools.py | 8 ++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Modules/onoff_settings.py b/Modules/onoff_settings.py index 952935695..d471fab24 100644 --- a/Modules/onoff_settings.py +++ b/Modules/onoff_settings.py @@ -14,7 +14,8 @@ from Modules.enki import is_enky_device from Modules.philips import is_philips_device from Modules.readAttributes import ReadAttributeRequest_0006_400x -from Modules.tools import get_deviceconf_parameter_value, getListOfEpForCluster +from Modules.tools import (get_deviceconf_parameter_value, + getListOfEpForCluster, is_int) from Modules.tuya import (get_tuya_attribute, is_tuya_switch_relay, tuya_switch_relay_status) from Modules.zigateConsts import ZIGATE_EP @@ -67,9 +68,13 @@ def onoff_startup_onoff_mode(self, nwkid, ep, value): self.log.logging( "onoffSettings", "Debug", f"onoff_startup_onoff_mode for {nwkid}/{ep} - value: {value}", nwkid ) if isinstance(value, str): - old_value = value - value = int(value) - self.log.logging( "onoffSettings", "Log", f"onoff_startup_onoff_mode for {nwkid}/{ep} - value: {old_value} converted to {value}", nwkid ) + if is_int(value): + old_value = value + value = int(value) + self.log.logging( "onoffSettings", "Log", f"onoff_startup_onoff_mode for {nwkid}/{ep} - value: {old_value} converted to {value}", nwkid ) + else: + self.log.logging( "onoffSettings", "Error", f"onoff_startup_onoff_mode for {nwkid}/{ep} - value error {value}", nwkid ) + return write_attribute( self, diff --git a/Modules/tools.py b/Modules/tools.py index f90cff3b1..f9920e1a0 100644 --- a/Modules/tools.py +++ b/Modules/tools.py @@ -26,10 +26,14 @@ from Modules.pluginDbAttributes import STORE_CONFIGURE_REPORTING from Modules.zigateConsts import HEARTBEAT +HEX_DIGIT = "0123456789abcdefABCDEF" +INT_DIGIT = "0123456789" def is_hex(s): - hex_digits = set("0123456789abcdefABCDEF") - return all(char in hex_digits for char in s) + return all(char in HEX_DIGIT for char in s) + +def is_int(s): + return all(char in INT_DIGIT for char in s) def returnlen(taille, value): while len(value) < taille: From 16cbdfa853d8b1c7fed5f3cbf69175cdf8717a82 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Sat, 17 Feb 2024 13:45:03 +0100 Subject: [PATCH 20/21] introduce Tuya Magic Read Attribute --- Modules/pairingProcess.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/pairingProcess.py b/Modules/pairingProcess.py index 5ac55ff77..167802812 100644 --- a/Modules/pairingProcess.py +++ b/Modules/pairingProcess.py @@ -624,6 +624,10 @@ def handle_device_specific_needs(self, Devices, NWKID): if "Model" not in self.ListOfDevices[NWKID]: return + # Do the Magic Read Attributes + if get_deviceconf_parameter_value(self, self.ListOfDevices[NWKID]["Model"], "TUYA_MAGIC_READ_ATTRIBUTES", return_default=False): + ReadAttributeRequest_0000_for_tuya( self, NWKID) + # Tuya_regitration ? tuya_registration_parameter = get_deviceconf_parameter_value(self, self.ListOfDevices[NWKID]["Model"], "TUYA_REGISTRATION", return_default=None) ty_data_request = get_deviceconf_parameter_value(self, self.ListOfDevices[NWKID]["Model"], "TUYA_RESET_CMD", return_default=False) From bf83c44d531c6e968aca6c157bb280b33a8c5228 Mon Sep 17 00:00:00 2001 From: Patrick Pichon Date: Thu, 22 Feb 2024 22:08:47 +0100 Subject: [PATCH 21/21] fix some lumi plug issues --- Modules/lumi.py | 145 ++++++++++++++++++++++------------------ Modules/readClusters.py | 18 +++-- 2 files changed, 93 insertions(+), 70 deletions(-) diff --git a/Modules/lumi.py b/Modules/lumi.py index ffccd5e28..9d814cfe9 100644 --- a/Modules/lumi.py +++ b/Modules/lumi.py @@ -17,8 +17,10 @@ from Modules.domoMaj import MajDomoDevice from Modules.domoTools import Update_Battery_Device from Modules.readAttributes import ReadAttributeRequest_0b04_050b -from Modules.tools import (checkAndStoreAttributeValue, getListOfEpForCluster, - is_ack_tobe_disabled, voltage2batteryP) +from Modules.tools import (checkAndStoreAttributeValue, + get_deviceconf_parameter_value, + getListOfEpForCluster, is_ack_tobe_disabled, + voltage2batteryP) from Modules.zigateConsts import MAX_LOAD_ZIGATE, SIZE_DATA_TYPE, ZIGATE_EP XIAOMI_POWERMETER_EP = { @@ -680,8 +682,6 @@ def lumi_private_cluster(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgA # Taging: https://github.com/dresden-elektronik/deconz-rest-plugin/issues/42#issuecomment-370152404 # 0x0624 might be the LQI indicator and 0x0521 the RSSI dB - # . 0328130521330008213 6010a2100000c2014102001122000 652001 662003 672000 682000 692001 6a2001 6b2003 - sBatteryLvl = retreive4Tag("0121", MsgClusterData) # 16BitUint sTemp2 = retreive4Tag("0328", MsgClusterData) # Device Temperature (int8) sModeSwitch = retreive4Tag("0421", MsgClusterData) # Mode Switch4: 'anti_flicker_mode', 1: 'quick_mode' @@ -704,7 +704,8 @@ def lumi_private_cluster(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgA sVoltage = retreive8Tag("9639", MsgClusterData) # Voltage sCurrent = retreive8Tag("9739", MsgClusterData) # Ampere sPower = retreive8Tag("9839", MsgClusterData) # Power Watt - + sConsumerConnected = retreive4Tag("9b10", MsgClusterData)[:2] + # "lumi.motion.ac01" # 0328180521010008213/ 6010a2100000c2014102001122000 652001/ 662003/ 672000/ 682000/ 692001/ 6a2001/ 6b2003 sPresence = retreive4Tag("6520", MsgClusterData)[:2] @@ -715,7 +716,6 @@ def lumi_private_cluster(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgA s6a = retreive4Tag("6a20", MsgClusterData)[:2] s6b = retreive4Tag("6b20", MsgClusterData)[:2] - if self.ListOfDevices[MsgSrcAddr]["Model"] == "lumi.motion.ac02": # "lumi.motion.ac02" sIlluminence = retreive4Tag("6521", MsgClusterData)[:4] @@ -745,7 +745,6 @@ def lumi_private_cluster(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgA self.log.logging( "Lumi", "Debug", "lumi_private_cluster - %s/%s Saddr: %s TriggerIndicator %s/%s" % ( MsgClusterId, MsgAttrID, MsgSrcAddr, sTriggerIndicator, int(sTriggerIndicator,16)), MsgSrcAddr, ) - if self.ListOfDevices[MsgSrcAddr]["Model"] == "lumi.motion.ac01": if s68 != "": store_lumi_attribute(self, MsgSrcAddr, "s68", s68) @@ -787,7 +786,7 @@ def lumi_private_cluster(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgA if self.ListOfDevices[MsgSrcAddr]["Model"] == "lumi.curtain.acn002": sBatteryLvl = retreive4Tag("6521", MsgClusterData) sHumid = "" - + if sCountEvent != "": value = int(sCountEvent, 16) store_lumi_attribute(self, MsgSrcAddr, "EventCounter", value) @@ -800,71 +799,90 @@ def lumi_private_cluster(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgA ) if sTemp2 != "": - self.log.logging( - "Lumi", - "Debug", - "lumi_private_cluster - %s/%s Saddr: %s sTemp2 %s Temp2 %s" - % (MsgClusterId, MsgAttrID, MsgSrcAddr, sTemp2, int(sTemp2, 16)), - MsgSrcAddr, - ) + self.log.logging( "Lumi", "Debug", "lumi_private_cluster - %s/%s Saddr: %s sTemp2 %s Temp2 %s" % ( + MsgClusterId, MsgAttrID, MsgSrcAddr, sTemp2, int(sTemp2, 16)), MsgSrcAddr,) store_lumi_attribute(self, MsgSrcAddr, "DeviceTemperature", round(int(sTemp2, 16) / 100, 1)) + if sConsumerConnected != "": + self.log.logging( "Lumi", "Debug", "lumi_private_cluster - %s/%s Saddr: %s sConsumerConnected %s" % ( + MsgClusterId, MsgAttrID, MsgSrcAddr, sConsumerConnected), MsgSrcAddr,) + store_lumi_attribute(self, MsgSrcAddr, "ConsumerConnected", sConsumerConnected) + if sConsumption != "": # Consumption/Summation - consumption = (struct.unpack("f", struct.pack(">I", int(sConsumption, 16)))[0]) * 1000 - self.log.logging( - "Lumi", - "Debug", - "lumi_private_cluster - %s/%s Saddr: %s sConsumption %s Consumption %s" - % (MsgClusterId, MsgAttrID, MsgSrcAddr, sConsumption, consumption), - ) + multiplier = get_deviceconf_parameter_value(self, self.ListOfDevices[MsgSrcAddr]["Model"], "SummationMeteringMultiplier") + divisor = get_deviceconf_parameter_value(self, self.ListOfDevices[MsgSrcAddr]["Model"], "SummationMeteringDivisor") + if multiplier is None: + multiplier = 1000 + if divisor is None: + divisor = 1 + + consumption = (struct.unpack("f", struct.pack(">I", int(sConsumption, 16)))[0]) + consumption = round( (( consumption * multiplier ) / divisor ), 3) + + self.log.logging( "Lumi", "Debug", "lumi_private_cluster - %s/%s Saddr: %s sConsumption %s Consumption %s Multiplier: %s Divisor: %s" % ( + MsgClusterId, MsgAttrID, MsgSrcAddr, sConsumption, consumption, multiplier, divisor ), ) store_lumi_attribute(self, MsgSrcAddr, "Consumption", consumption) - if model in XIAOMI_POWERMETER_EP: - EPforMeter = XIAOMI_POWERMETER_EP[model] - else: - EPforMeter = MsgSrcEp - checkAndStoreAttributeValue(self, MsgSrcAddr, EPforMeter, "0702", "0000", consumption) + + EPforPower = get_xiaomi_metering_ep( self, MsgSrcAddr, MsgSrcEp, model ) + checkAndStoreAttributeValue(self, MsgSrcAddr, EPforPower, "0702", "0000", consumption) if sVoltage != "": + divisor = get_deviceconf_parameter_value(self, self.ListOfDevices[MsgSrcAddr]["Model"], "RMSVoltageDivisor") + multiplier = get_deviceconf_parameter_value(self, self.ListOfDevices[MsgSrcAddr]["Model"], "RMSVoltageMultiplier") + if multiplier is None: + multiplier = 1 + if divisor is None: + divisor = 1 voltage = struct.unpack("f", struct.pack(">I", int(sVoltage, 16)))[0] - self.log.logging( - "Lumi", "Debug", "lumi_private_cluster - %s/%s Saddr: %s Voltage %s" % (MsgClusterId, MsgAttrID, MsgSrcAddr, voltage) - ) + voltage = round( (( voltage * multiplier ) / divisor ), 3) + + self.log.logging( "Lumi", "Debug", "lumi_private_cluster - %s/%s Saddr: %s Voltage %s multiplier %s divisor %s" % ( + MsgClusterId, MsgAttrID, MsgSrcAddr, voltage, multiplier, divisor ) ) checkAndStoreAttributeValue(self, MsgSrcAddr, MsgSrcEp, "0001", "0000", voltage) store_lumi_attribute(self, MsgSrcAddr, "Voltage", voltage) # Update Voltage ( cluster 0001 ) MajDomoDevice(self, Devices, MsgSrcAddr, MsgSrcEp, "0001", voltage) if sCurrent != "": + divisor = get_deviceconf_parameter_value(self, self.ListOfDevices[MsgSrcAddr]["Model"], "RMSCurrentDivisor") + multiplier = get_deviceconf_parameter_value(self, self.ListOfDevices[MsgSrcAddr]["Model"], "RMSCurrentMultiplier") + if multiplier is None: + multiplier = 1 + if divisor is None: + divisor = 1 + current = struct.unpack("f", struct.pack(">I", int(sCurrent, 16)))[0] - self.log.logging( - "Lumi", "Debug", "lumi_private_cluster - %s/%s Saddr: %s Courant %s" % (MsgClusterId, MsgAttrID, MsgSrcAddr, current) - ) + current = round( (( current * multiplier ) / divisor ), 3 ) + + self.log.logging( "Lumi", "Debug", "lumi_private_cluster - %s/%s Saddr: %s Courant %s %s multiplier %s divisor %s" % ( + MsgClusterId, MsgAttrID, MsgSrcAddr, sCurrent, current, multiplier, divisor ) ) + store_lumi_attribute(self, MsgSrcAddr, "Current", current) + MajDomoDevice(self, Devices, MsgSrcAddr, MsgSrcEp, "0b04", current, Attribute_="0508" ) if sPower != "": + multiplier = get_deviceconf_parameter_value(self, self.ListOfDevices[MsgSrcAddr]["Model"], "PowerMeteringMultiplier") + divisor = get_deviceconf_parameter_value(self, self.ListOfDevices[MsgSrcAddr]["Model"], "PowerMeteringDivisor") + if multiplier is None: + multiplier = 1 + if divisor is None: + divisor = 1 + # Instant Power power = struct.unpack("f", struct.pack(">I", int(sPower, 16)))[0] + power = round( (( power * multiplier ) / divisor ), 3) if power > 0x7FFFFFFFFFFFFFFF: - self.log.logging( - "Lumi", - "Eror", - "lumi_private_cluster - %s/%s Saddr: %s sPower %s Power %s (Overflow)" - % (MsgClusterId, MsgAttrID, MsgSrcAddr, sPower, power), - ) + self.log.logging( "Lumi", "Eror", "lumi_private_cluster - %s/%s Saddr: %s sPower %s Power %s (Overflow)" % (MsgClusterId, MsgAttrID, MsgSrcAddr, sPower, power), ) return - self.log.logging( - "Lumi", - "Debug", - "lumi_private_cluster - %s/%s Saddr: %s sPower %s Power %s" % (MsgClusterId, MsgAttrID, MsgSrcAddr, sPower, power), - ) + self.log.logging( "Lumi", "Debug", "lumi_private_cluster - %s/%s Saddr: %s sPower %s Power %s multiplier %s divisor %s" % ( + MsgClusterId, MsgAttrID, MsgSrcAddr, sPower, power, multiplier, divisor ) ) + store_lumi_attribute(self, MsgSrcAddr, "Power", power) - if model in XIAOMI_POWERMETER_EP: - EPforPower = XIAOMI_POWERMETER_EP[model] - else: - EPforPower = MsgSrcEp + + EPforPower = get_xiaomi_metering_ep( self, MsgSrcAddr, MsgSrcEp, model ) checkAndStoreAttributeValue(self, MsgSrcAddr, EPforPower, "0702", "0400", str(power)) - # Update Power Widget + MajDomoDevice(self, Devices, MsgSrcAddr, EPforPower, "0702", str(power)) if sLighLevel != "": @@ -893,12 +911,7 @@ def lumi_private_cluster(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgA ) store_lumi_attribute(self, MsgSrcAddr, "LQI", sLQI) - if ( - sBatteryLvl != "" - and self.ListOfDevices[MsgSrcAddr]["MacCapa"] != "8e" - and self.ListOfDevices[MsgSrcAddr]["MacCapa"] != "84" - and self.ListOfDevices[MsgSrcAddr]["PowerSource"] != "Main" - ): + if ( sBatteryLvl != "" and self.ListOfDevices[MsgSrcAddr]["MacCapa"] != "8e" and self.ListOfDevices[MsgSrcAddr]["MacCapa"] != "84" and self.ListOfDevices[MsgSrcAddr]["PowerSource"] != "Main" ): voltage = "%s%s" % (str(sBatteryLvl[2:4]), str(sBatteryLvl[:2])) voltage = int(voltage, 16) ValueBattery = voltage2batteryP(voltage, 3150, 2750) @@ -1010,9 +1023,15 @@ def lumi_cluster_fcc0(self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgAttr else: self.log.logging( "Lumi", "Debug", "lumi_private_cluster %s - %s/%s Unknown attribute: %s value %s" % (MsgClusterId, MsgSrcAddr, MsgSrcEp, MsgAttrID, MsgClusterData), MsgSrcAddr, ) store_lumi_attribute(self, MsgSrcAddr, MsgAttrID , MsgClusterData) - - - + + +def get_xiaomi_metering_ep( self, nwkid, ep, model ): + xiaomi_meter_ep = get_deviceconf_parameter_value(self, self.ListOfDevices[nwkid]["Model"], "XIAOMI_METER_EP") + if xiaomi_meter_ep: + return xiaomi_meter_ep + return XIAOMI_POWERMETER_EP[model] if model in XIAOMI_POWERMETER_EP else ep + + def cube_decode(self, value, MsgSrcAddr): "https://github.com/sasu-drooz/Domoticz-Zigate/wiki/Aqara-Cube-decoding" value = int(value, 16) @@ -1085,11 +1104,7 @@ def decode_vibrAngle(rawData): def store_lumi_attribute(self, NwkId, Attribute, Value): - - if "LUMI" not in self.ListOfDevices[NwkId]: - self.ListOfDevices[NwkId]["LUMI"] = {} - self.ListOfDevices[NwkId]["LUMI"][Attribute] = Value - + self.ListOfDevices[NwkId].setdefault("LUMI", {})[Attribute] = Value LUMI_DEVICE_PARAMETERS = { "vibrationAqarasensitivity": setXiaomiVibrationSensitivity, @@ -1107,6 +1122,4 @@ def store_lumi_attribute(self, NwkId, Attribute, Value): "RTCZCGQ11LMApproachDistance": RTCZCGQ11LM_motion_opple_approach_distance, "RTCZCGQ11LMMonitoringMode": RTCZCGQ11LM_motion_opple_monitoring_mode, "RTCGQ14LMTriggerIndicator": RTCGQ14LM_trigger_indicator, - - -} +} \ No newline at end of file diff --git a/Modules/readClusters.py b/Modules/readClusters.py index 3f2fb2883..02a859404 100644 --- a/Modules/readClusters.py +++ b/Modules/readClusters.py @@ -599,22 +599,32 @@ def Cluster000c(self, Devices, MsgSQN, MsgSrcAddr, MsgSrcEp, MsgClusterId, MsgAt self.log.logging("Cluster", "Debug", "%s/%s Out of service: %s" % (MsgSrcAddr, MsgSrcEp, MsgClusterData), MsgSrcAddr) elif MsgAttrID == "0055": # The PresentValueattribute indicates the current value of the input, output or value + xiaomi_power_endpoint = get_deviceconf_parameter_value(self, self.ListOfDevices[MsgSrcAddr]["Model"], "XIAOMI_POWER_EP") + if self.ListOfDevices[MsgSrcAddr]["Model"] == "lumi.airmonitor.acn01": voc = decodeAttribute(self, MsgAttType, MsgClusterData) - self.log.logging("Cluster", "Log", "%s/%s Voc: %s" % (MsgSrcAddr, MsgSrcEp, voc), MsgSrcAddr) + self.log.logging("Cluster", "Debug", "%s/%s Voc: %s" % (MsgSrcAddr, MsgSrcEp, voc), MsgSrcAddr) if not checkValidValue(self, MsgSrcAddr, MsgAttType, voc): - self.log.logging( "Cluster", "Info", "Voc - invalid Data Value found : %s" % ( + self.log.logging( "Cluster", "Debug", "Voc - invalid Data Value found : %s" % ( voc), MsgSrcAddr, ) return MajDomoDevice( self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, voc) return - + + if xiaomi_power_endpoint: + # The Power received via 0x0055 + value = round(float(decodeAttribute(self, MsgAttType, MsgClusterData)), 3) + self.log.logging( "Cluster", "Debug", "readCluster - %s - %s/%s Xiaomi Power via PresentValue: %s: %s " % ( + MsgClusterId, MsgSrcAddr, MsgSrcEp, MsgAttrID, value), MsgSrcAddr, ) + MajDomoDevice(self, Devices, MsgSrcAddr, xiaomi_power_endpoint, "0702", str(value)) # For to Power Cluster + return + if getEPforClusterType(self, MsgSrcAddr, "Analog") and MsgAttType == "39": # We have an Analog Widget created, so we can consider it is not a Xiaomi Plug nor an Aqara/XCube self.log.logging( "Cluster", "Debug", "readCluster - %s - %s/%s Xiaomi attribute: %s: %s " % ( MsgClusterId, MsgSrcAddr, MsgSrcEp, MsgAttrID, decodeAttribute(self, MsgAttType, MsgClusterData)), MsgSrcAddr, ) if not checkValidValue(self, MsgSrcAddr, MsgAttType, MsgClusterData): - self.log.logging( "Cluster", "Info", "Cluster000c - MsgAttrID: %s MsgAttType: %s DataLen: %s : invalid Data Value found : %s" % ( + self.log.logging( "Cluster", "Debug", "Cluster000c - MsgAttrID: %s MsgAttType: %s DataLen: %s : invalid Data Value found : %s" % ( MsgAttrID, MsgAttType, MsgAttSize, MsgClusterData), MsgSrcAddr, ) return MajDomoDevice( self, Devices, MsgSrcAddr, MsgSrcEp, MsgClusterId, str(decodeAttribute(self, MsgAttType, MsgClusterData)),)