From ba1b7767a7c37907edf49a6848d204cd03aac7da Mon Sep 17 00:00:00 2001 From: Pipiche <8291674+pipiche38@users.noreply.github.com> Date: Sun, 29 Oct 2023 12:28:30 +0100 Subject: [PATCH] Lighting color control / Fixing Lidl TS0505A behaviour (#1657) * TS0505A exist with different Manufacturer Name and it seems that they are not controlled on the same way. --- Conf/Local-Devices/TS0505A-HueSaturation.json | 55 ++++++++ Conf/Local-Devices/TS0505A.json | 50 +++++++ Conf/Local-Devices/TS0601-Human-Presence.json | 43 ++++++ Modules/actuators.py | 128 ++++++++++++++---- Modules/basicOutputs.py | 7 +- Modules/command.py | 4 +- Modules/pairingProcess.py | 16 ++- Modules/readAttributes.py | 7 + Modules/tuya.py | 61 +++++++-- Zigbee/zclCommands.py | 6 + Zigbee/zclRawCommands.py | 28 ++++ 11 files changed, 357 insertions(+), 48 deletions(-) create mode 100644 Conf/Local-Devices/TS0505A-HueSaturation.json create mode 100644 Conf/Local-Devices/TS0505A.json create mode 100644 Conf/Local-Devices/TS0601-Human-Presence.json diff --git a/Conf/Local-Devices/TS0505A-HueSaturation.json b/Conf/Local-Devices/TS0505A-HueSaturation.json new file mode 100644 index 000000000..2ba4a64d8 --- /dev/null +++ b/Conf/Local-Devices/TS0505A-HueSaturation.json @@ -0,0 +1,55 @@ +{ + "_comment": "Tuya RR400ZB require Tuya HueandSaturation", + "_blakadder": "https://zigbee.blakadder.com/Lidl_HG06104A.html", + "_version": "1.0", + "Identifier" : [ + ["TS0505A", "_TZ3000_dbou1ap4"] + ], + "Ep": { + "01": { + "0000": "", + "0003": "", + "0004": "", + "0005": "", + "0006": "", + "0008": "", + "0300": { + "Attributes": { + "f000": {"Enabled": true, "Name": "ColorMode", "DataType": "20", "ActionList": [ "check_store_value"] }, + "f001": {"Enabled": true, "Name": "Brightness ", "DataType": "20", "ActionList": [ "check_store_value"] }, + "f003": {"Enabled": true, "Name": "SceneData", "DataType": "48", "ActionList": [ "check_store_value"] } + } + }, + "000a": "", + "0019": "", + "1000": "", + "ef00": "", + "Type": "ColorControlRGBWW" + }, + "f2": { + "0021": "", + "Type": "" + } + }, + "Type": "", + "bindEp": "01", + "ClusterToBind": [ ], + "ConfigureReporting": { + }, + "ReadAttributes": { + "0000": [ "0000", "0001", "0002", "0003", "0004", "0005", "0006", "0007" ], + "0006": [ "0000", "4001", "4002" ], + "0008": [ "0000" ], + "0019": [], + "0300": [ "f002", "f00d", "0000", "0001", "0003", "0004", "0007", "0008", "000f" ] + }, + "TUYA_REGISTRATION": 13, + "TuyaCommandF0": true, + "FORCE_COLOR_COMMAND": "TuyaMovetoHueandSaturation", + "TUYAColorControlRgbMode": true, + "Param": { + "moveToColourTemp": "0010", + "moveToColourRGB": "0010", + "moveToHueSatu": "0000" + } +} diff --git a/Conf/Local-Devices/TS0505A.json b/Conf/Local-Devices/TS0505A.json new file mode 100644 index 000000000..48f6b33c1 --- /dev/null +++ b/Conf/Local-Devices/TS0505A.json @@ -0,0 +1,50 @@ +{ + "_comment": "Tuya RR400ZB able to manage color via MoveToColour", + "_blakadder": "https://zigbee.blakadder.com/Lidl_HG06104A.html", + "_version": "1.0", + "Ep": { + "01": { + "0000": "", + "0003": "", + "0004": "", + "0005": "", + "0006": "", + "0008": "", + "0300": { + "Attributes": { + "f000": {"Enabled": true, "Name": "Tuya0300_f000", "DataType": "20", "ActionList": [ "check_store_value"] }, + "f003": {"Enabled": true, "Name": "Tuya0300_f003", "DataType": "48", "ActionList": [ "check_store_value"] } + } + }, + "000a": "", + "0019": "", + "1000": "", + "ef00": "", + "Type": "ColorControlRGBWW" + }, + "f2": { + "0021": "", + "Type": "" + } + }, + "Type": "", + "bindEp": "01", + "ClusterToBind": [ ], + "ConfigureReporting": { + }, + "ReadAttributes": { + "0000": [ "0000", "0001", "0002", "0003", "0004", "0005", "0006", "0007" ], + "0006": [ "0000", "4001", "4002" ], + "0008": [ "0000" ], + "0019": [], + "0300": [ "f002", "f00d", "0000", "0001", "0003", "0004", "0007", "0008", "000f" ] + }, + "TUYA_REGISTRATION": 13, + "TuyaCommandF0": true, + "TUYAColorControlRgbMode": true, + "Param": { + "moveToColourTemp": "0010", + "moveToColourRGB": "0010", + "moveToHueSatu": "0000" + } +} diff --git a/Conf/Local-Devices/TS0601-Human-Presence.json b/Conf/Local-Devices/TS0601-Human-Presence.json new file mode 100644 index 000000000..13f120a1c --- /dev/null +++ b/Conf/Local-Devices/TS0601-Human-Presence.json @@ -0,0 +1,43 @@ +{ + "_source": "https://github.com/zigbeefordomoticz/z4d-certified-devices/issues/11", + "_blakadder": "https://zigbee.blakadder.com/Tuya_ZY-M100-24G.html", + "_description": "uya Human Presence Detector 5.8GHz Ceiling Mount ZY-M100-2", + "Ep": { + "01": { + "0000": "", + "0004": "", + "0005": "", + "0500": "", + "000a": "", + "0019": "", + "Type": "Motion/Lux" + } + }, + "Identifier": [ + [ "TS0601", "_TZE200_bh3n6gk8" ], + [ "TS0601", "_TZE200_3towulqd" ], + [ "TS0601", "_TZE200_1ibpyhdc" ] + ], + "Type": "", + "ClusterToBind": [ ], + "ConfigureReporting": {}, + "ReadAttributes": { + "0000": [ "0004", "0000", "0001", "0005", "0007", "fffe" ], + "0001": [], + "0019": [], + "0500": [ "0000", "0001", "0002", "0010", "0011"], + "ef00": [] + }, + "TS0601_DP": { + "68": { "sensor_type": "illuminance"}, + "69": { "sensor_type": "motion", "EvalExp": "int(value == 0)", "DomoDeviceFormat": "str"}, + "6a": { "store_tuya_attribute": "Sensitivity"}, + "6b": { "store_tuya_attribute": "max_range"}, + "6c": { "store_tuya_attribute": "min_range"}, + "6d": { "store_tuya_attribute": "target_distace"}, + "6e": { "store_tuya_attribute": "fading_time"}, + "6f": { "store_tuya_attribute": "detection_delay"} + }, + "Param": { + } +} diff --git a/Modules/actuators.py b/Modules/actuators.py index bcfbdecf5..9f7bba38c 100644 --- a/Modules/actuators.py +++ b/Modules/actuators.py @@ -13,9 +13,16 @@ import json from Modules.basicOutputs import set_poweron_afteroffon -from Modules.readAttributes import ReadAttributeRequest_0006_400x +from Modules.paramDevice import get_device_config_param +from Modules.readAttributes import ( + ReadAttributeRequest_0006_400x, + ReadAttributeRequest_0300_Color_Capabilities) from Modules.thermostats import thermostat_Setpoint -from Modules.tools import Hex_Format, rgb_to_hsl, rgb_to_xy +from Modules.tools import (Hex_Format, get_deviceconf_parameter_value, + getAttributeValue, rgb_to_hsl, rgb_to_xy) +from Modules.tuya import (tuya_color_control_rgbMode, + tuya_Move_To_Hue_Saturation, + tuya_Move_To_Hue_Saturation_Brightness) from Modules.zigateConsts import ZIGATE_EP from Zigbee.zclCommands import (zcl_identify_send, zcl_identify_trigger_effect, zcl_level_move_to_level, @@ -238,34 +245,34 @@ def actuator_setcolor(self, nwkid, EPout, value, Color): # First manage level # if Hue_List['m'] or Hue_List['m'] != 9998 or manage_level: transitionMoveLevel = "0000" - if ( - "Param" in self.ListOfDevices[nwkid] - and "moveToLevel" in self.ListOfDevices[nwkid]["Param"] - ): + if ( "Param" in self.ListOfDevices[nwkid] and "moveToLevel" in self.ListOfDevices[nwkid]["Param"] ): transitionMoveLevel = "%04x" % int(self.ListOfDevices[nwkid]["Param"]["moveToLevel"]) - - if Hue_List["m"] or Hue_List["m"] != 9998: - value = lightning_percentage_to_analog( value ) - self.log.logging("Command", "Debug", "---------- Set Level: %s" % (value), nwkid) - actuator_setlevel(self, nwkid, EPout, value, "Light", transitionMoveLevel) - # ColorModeTemp = 2 // White with color temperature. Valid fields: t + force_color_command = get_deviceconf_parameter_value(self, self.ListOfDevices[nwkid]["Model"], "FORCE_COLOR_COMMAND", return_default=None) + ColorCapabilitiesList = device_color_capabilities( self, nwkid, EPout) + self.log.logging("Command", "Debug", "actuator_setcolor force_color_command %s" % force_color_command, nwkid) + if Hue_List["m"] == 2: + # ColorModeTemp = 2 // White with color temperature. Valid fields: t handle_color_mode_2(self, nwkid, EPout, Hue_List) - # ColorModeRGB = 3 // Color. Valid fields: r, g, b. + elif Hue_List["m"] == 3 and force_color_command == "TuyaMovetoHueandSaturation": + handle_color_mode_tuya( self, nwkid, EPout, Hue_List, value) + elif Hue_List["m"] == 3: + # ColorModeRGB = 3 // Color. Valid fields: r, g, b. handle_color_mode_3(self, nwkid, EPout, Hue_List) - # ColorModeCustom = 4, // Custom (color + white). Valid fields: r, g, b, cw, ww, depending on device capabilities elif Hue_List["m"] == 4: + # ColorModeCustom = 4, // Custom (color + white). Valid fields: r, g, b, cw, ww, depending on device capabilities handle_color_mode_4(self, nwkid, EPout, Hue_List ) - - # With saturation and hue, not seen in domoticz but present on zigate, and some device need it + elif Hue_List["m"] == 9998: - handle_color_mode_9998( self, nwkid, EPout, Hue_List) + # With saturation and hue, not seen in domoticz but present on zigate, and some device need it + handle_color_mode_9998( self, nwkid, EPout, Hue_List, value) def handle_color_mode_2(self, nwkid, EPout, Hue_List): + # White with color temperature. Valid fields: t # Value is in mireds (not kelvin) # Correct values are from 153 (6500K) up to 588 (1700K) # t is 0 > 255 @@ -274,8 +281,10 @@ def handle_color_mode_2(self, nwkid, EPout, Hue_List): self.log.logging( "Command", "Debug", "handle_color_mode_2 Set Temp Kelvin: %s-%s" % (TempMired, Hex_Format(4, TempMired)), nwkid ) transitionMoveLevel , transitionRGB , transitionMoveLevel , transitionHue , transitionTemp = get_all_transition_mode( self, nwkid) zcl_move_to_colour_temperature( self, nwkid, EPout, Hex_Format(4, TempMired), transitionTemp) + def handle_color_mode_3(self, nwkid, EPout, Hue_List): + # Color. Valid fields: r, g, b. x, y = rgb_to_xy((int(Hue_List["r"]), int(Hue_List["g"]), int(Hue_List["b"]))) # Convert 0>1 to 0>FFFF x = int(x * 65536) @@ -283,6 +292,8 @@ def handle_color_mode_3(self, nwkid, EPout, Hue_List): #strxy = Hex_Format(4, x) + Hex_Format(4, y) self.log.logging("Command", "Debug", "handle_color_mode_3 Set Temp X: %s Y: %s" % (x, y), nwkid) transitionMoveLevel , transitionRGB , transitionMoveLevel , transitionHue , transitionTemp = get_all_transition_mode( self, nwkid) + if get_deviceconf_parameter_value(self, self.ListOfDevices[nwkid]["Model"], "TUYAColorControlRgbMode", return_default=None): + tuya_color_control_rgbMode( self, nwkid, "01") zcl_move_to_colour(self, nwkid, EPout, Hex_Format(4, x), Hex_Format(4, y), transitionRGB) def handle_color_mode_4(self, nwkid, EPout, Hue_List ): @@ -296,9 +307,10 @@ def handle_color_mode_4(self, nwkid, EPout, Hue_List ): if cw != 0 and ww != 0: TempKelvin = int((255 - ww) * (6500 - 1700) / 255 + 1700) TempMired = 1000000 // TempKelvin - self.log.logging( - "Command", "Log", "handle_color_mode_4 Set Temp Kelvin: %s-%s" % (TempMired, Hex_Format(4, TempMired)), nwkid - ) + self.log.logging( "Command", "Log", "handle_color_mode_4 Set Temp Kelvin: %s-%s" % ( + TempMired, Hex_Format(4, TempMired)), nwkid ) + if get_deviceconf_parameter_value(self, self.ListOfDevices[nwkid]["Model"], "TUYAColorControlRgbMode", return_default=None): + tuya_color_control_rgbMode( self, nwkid, "01") zcl_move_to_colour_temperature( self, nwkid, EPout, Hex_Format(4, TempMired), transitionTemp) # Process Colour @@ -308,10 +320,13 @@ def handle_color_mode_4(self, nwkid, EPout, Hue_List ): hue = _h * 360 # 0 > 360 hue = int(hue * 254 // 360) - self.log.logging("Command", "Log", "handle_color_mode_4 Set Hue X: %s Saturation: %s" % (hue, saturation), nwkid) + self.log.logging("Command", "Log", "handle_color_mode_4 Set Hue X: %s Saturation: %s" % ( + hue, saturation), nwkid) + if get_deviceconf_parameter_value(self, self.ListOfDevices[nwkid]["Model"], "TUYAColorControlRgbMode", return_default=None): + tuya_color_control_rgbMode( self, nwkid, "01") zcl_move_hue_and_saturation(self, nwkid, EPout, Hex_Format(2, hue), Hex_Format(2, saturation), transitionRGB) - -def handle_color_mode_9998( self, nwkid, EPout, Hue_List): + +def handle_color_mode_9998( self, nwkid, EPout, Hue_List, value): transitionMoveLevel , transitionRGB , transitionMoveLevel , transitionHue , transitionTemp = get_all_transition_mode( self, nwkid) _h, _s, _l = rgb_to_hsl((int(Hue_List["r"]), int(Hue_List["g"]), int(Hue_List["b"]))) saturation = _s * 100 # 0 > 100 @@ -320,12 +335,35 @@ def handle_color_mode_9998( self, nwkid, EPout, Hue_List): hue = int(hue * 254 // 360) self.log.logging("Command", "Debug", "handle_color_mode_9998 Set Hue X: %s Saturation: %s" % (hue, saturation), nwkid) - zcl_move_hue_and_saturation(self, nwkid, EPout, Hex_Format(2, hue), Hex_Format(2, saturation), transitionRGB) + if get_deviceconf_parameter_value(self, self.ListOfDevices[nwkid]["Model"], "TUYAColorControlRgbMode", return_default=None): + tuya_color_control_rgbMode( self, nwkid, "01") + + if get_deviceconf_parameter_value(self, self.ListOfDevices[nwkid]["Model"], "FORCE_COLOR_COMMAND", return_default=None) == "TuyaMovetoHueandSaturation": + tuya_Move_To_Hue_Saturation( self, nwkid, hue, saturation, value) + else: + zcl_move_hue_and_saturation(self, nwkid, EPout, Hex_Format(2, hue), Hex_Format(2, saturation), transitionRGB) + value = lightning_percentage_to_analog( value ) + self.log.logging( "Command", "Debug", "handle_color_mode_9998 Set Level: %s instead of Level: %s" % (value, value), nwkid) + actuator_setlevel(self, nwkid, EPout, value, "Light", transitionMoveLevel) + - value = int(_l * 254 // 100) - OnOff = "01" - self.log.logging( "Command", "Debug", "handle_color_mode_9998 Set Level: %s instead of Level: %s" % (value, value), nwkid) - actuator_setlevel(self, nwkid, EPout, value, "Light", transitionMoveLevel) +def handle_color_mode_tuya( self, nwkid, EPout, Hue_List, value): + + self.log.logging("Command", "Debug", "handle_color_mode_tuya Hue_list: %s Value: %s" % ( + Hue_List, value), nwkid) + + transitionMoveLevel , transitionRGB , transitionMoveLevel , transitionHue , transitionTemp = get_all_transition_mode( self, nwkid) + _h, _s, _ = rgb_to_hsl((int(Hue_List["r"]), int(Hue_List["g"]), int(Hue_List["b"]))) + saturation = _s * 100 # 0 > 100 + saturation = int(saturation * 254 // 100) + hue = _h * 360 # 0 > 360 + hue = int(hue * 254 // 360) + + self.log.logging("Command", "Log", "handle_color_mode_tuya Set Hue X: %s Saturation: %s Value: %s" % ( + hue, saturation, value), nwkid) + if get_deviceconf_parameter_value(self, self.ListOfDevices[nwkid]["Model"], "TUYAColorControlRgbMode", return_default=None): + tuya_color_control_rgbMode( self, nwkid, "01") + tuya_Move_To_Hue_Saturation( self, nwkid, EPout, hue, saturation, transitionHue, value ) def actuator_identify(self, nwkid, ep, value=None): @@ -351,4 +389,36 @@ def actuator_identify(self, nwkid, ep, value=None): value = 0x00 # Flashing color = 0x03 # Blue - zcl_identify_trigger_effect( self, nwkid, ep, "%02x" % value, "%02x" % color) \ No newline at end of file + zcl_identify_trigger_effect( self, nwkid, ep, "%02x" % value, "%02x" % color) + + +def decode_color_capabilities(capabilities_value): + capabilities = { + "Hue and Saturation": 0b00000000_00000001, + "Enhanced Hue": 0b00000000_00000010, + "Color Loop": 0b00000000_00000100, + "XY Attributes": 0b00000000_00001000, + "Color Temperature": 0b00000000_00010000 + } + + return [ + feature + for feature, bitmask in capabilities.items() + if capabilities_value & bitmask + ] + +def device_color_capabilities( self, nwkid, ep): + self.log.logging( "Command", "Debug", "device_color_capabilities %s %s" % (nwkid, ep), nwkid) + deviceHasNoColorCapabilities = get_deviceconf_parameter_value(self, self.ListOfDevices[nwkid]["Model"], "NoColorCapabilitie", return_default=None) + colorCapabilities = getAttributeValue( self, nwkid, ep, "0300", "400a") + + self.log.logging( "Command", "Debug", "+ deviceHasColorCapabilities %s" % (deviceHasNoColorCapabilities), nwkid) + self.log.logging( "Command", "Debug", "+ colorCapabilities %s" % (colorCapabilities), nwkid) + + if colorCapabilities is None and deviceHasNoColorCapabilities: + return [] + if colorCapabilities is None or isinstance( colorCapabilities, str): + ReadAttributeRequest_0300_Color_Capabilities(self, nwkid) + return [] + + return decode_color_capabilities(colorCapabilities) diff --git a/Modules/basicOutputs.py b/Modules/basicOutputs.py index 143240a3c..33799407b 100644 --- a/Modules/basicOutputs.py +++ b/Modules/basicOutputs.py @@ -34,7 +34,8 @@ from Zigbee.zclCommands import (zcl_attribute_discovery_request, zcl_get_list_attribute_extended_infos, zcl_identify_send, zcl_identify_trigger_effect, - zcl_read_attribute, zcl_write_attribute, + zcl_read_attribute, zcl_reset_device, + zcl_write_attribute, zcl_write_attributeNoResponse) from Zigbee.zdpCommands import (zdp_get_permit_joint_status, zdp_IEEE_address_request, @@ -42,7 +43,7 @@ zdp_management_network_update_request, zdp_many_to_one_route_request, zdp_permit_joining_request, - zdp_raw_nwk_update_request, zdp_reset_device) + zdp_raw_nwk_update_request) from Zigbee.zdpRawCommands import (zdp_management_binding_table_request, zdp_management_routing_table_request) @@ -417,7 +418,7 @@ def reset_device(self, nwkid, epout): self.log.logging("BasicOutput", "Debug", "reset_device - Send a Device Reset to %s/%s" % (nwkid, epout), nwkid) #return send_zigatecmd_raw(self, "0050", "02" + nwkid + ZIGATE_EP + epout) - return zdp_reset_device(self, nwkid, ZIGATE_EP, epout) + return zcl_reset_device(self, nwkid, ZIGATE_EP, epout) def leaveRequest(self, ShortAddr=None, IEEE=None, RemoveChild=0x00, Rejoin=0x00): diff --git a/Modules/command.py b/Modules/command.py index 08022294a..c3b7cbf14 100644 --- a/Modules/command.py +++ b/Modules/command.py @@ -28,8 +28,8 @@ schneider_set_contract, schneider_temp_Setcurrent) from Modules.switchSelectorWidgets import SWITCH_SELECTORS -from Modules.tools import get_deviceconf_parameter_value from Modules.thermostats import thermostat_Mode, thermostat_Setpoint +from Modules.tools import get_deviceconf_parameter_value from Modules.tuya import (tuya_curtain_lvl, tuya_curtain_openclose, tuya_dimmer_dimmer, tuya_dimmer_onoff, tuya_energy_onoff, tuya_garage_door_action, @@ -45,7 +45,6 @@ from Modules.zigateConsts import (THERMOSTAT_LEVEL_2_MODE, THERMOSTAT_LEVEL_3_MODE, ZIGATE_EP) - # Matrix between Domoticz Type, Subtype, SwitchType and Plugin DeviceType # Type, Subtype, Switchtype DEVICE_SWITCH_MATRIX = { @@ -1358,6 +1357,7 @@ def mgtCommand(self, Devices, Unit, Command, Level, Color): self.log.logging( "Command", "Debug", "mgtCommand : Set Color for Device: %s EPout: %s Unit: %s DeviceType: %s Level: %s Color: %s" % ( NWKID, EPout, Unit, DeviceType, Level, Color), NWKID, ) + actuator_setcolor(self, NWKID, EPout, Level, Color) request_read_device_status(self, NWKID) diff --git a/Modules/pairingProcess.py b/Modules/pairingProcess.py index 6cc2fe2ed..3ba8f5bbc 100644 --- a/Modules/pairingProcess.py +++ b/Modules/pairingProcess.py @@ -36,7 +36,8 @@ from Modules.thermostats import thermostat_Calibration from Modules.tools import (get_deviceconf_parameter_value, getListOfEpForCluster, is_fake_ep) -from Modules.tuya import tuya_cmd_ts004F, tuya_command_f0, tuya_registration +from Modules.tuya import (tuya_cmd_ts004F, tuya_command_f0, + tuya_lighting_color_control, tuya_registration) from Modules.tuyaConst import TUYA_eTRV_MODEL from Modules.tuyaSiren import tuya_sirene_registration from Modules.tuyaTools import tuya_TS0121_registration @@ -627,7 +628,7 @@ def handle_device_specific_needs(self, Devices, NWKID): if self.ListOfDevices[NWKID]["Model"] in ("Wiser2-Thermostat",): wiser_home_lockout_thermostat(self, NWKID, 0) - elif get_device_config_param( self, NWKID, "AqaraMultiClick"): + elif get_device_config_param( self, NWKID, "AqaraMultiClick"): enable_click_mode_aqara( self, NWKID) elif ( MsgIEEE[: PREFIX_MAC_LEN] in PREFIX_MACADDR_WIZER_LEGACY and WISER_LEGACY_MODEL_NAME_PREFIX in self.ListOfDevices[NWKID]["Model"] ): @@ -661,7 +662,8 @@ def handle_device_specific_needs(self, Devices, NWKID): elif self.ListOfDevices[NWKID]["Model"] in ( "TS0222", "TS0002_relay_switch", "TS0003_relay_switch", 'TS0601-motion'): tuya_command_f0( self, NWKID ) - + + elif self.ListOfDevices[NWKID]["Model"] in ( "TS0601-Energy", "TS0601-switch", @@ -691,7 +693,13 @@ def handle_device_specific_needs(self, Devices, NWKID): elif self.ListOfDevices[NWKID]["Model"] == "lumi.remote.b28ac1": enable_click_mode_aqara( self, NWKID ) enableOppleSwitch( self, NWKID ) - + + if get_deviceconf_parameter_value( self, self.ListOfDevices[NWKID]["Model"], "TuyaCommandF0"): + tuya_command_f0( self, NWKID ) + + if get_deviceconf_parameter_value(self, self.ListOfDevices[NWKID]["Model"], "LightingColorControl", return_default=None): + tuya_lighting_color_control(self, NWKID) + def scan_device_for_group_memebership(self, NWKID): for ep in self.ListOfDevices[NWKID]["Ep"]: if "0004" in self.ListOfDevices[NWKID]["Ep"][ep] and self.groupmgt: diff --git a/Modules/readAttributes.py b/Modules/readAttributes.py index 6e5a33195..5b67c4dc2 100644 --- a/Modules/readAttributes.py +++ b/Modules/readAttributes.py @@ -999,7 +999,14 @@ def ReadAttributeRequest_0300(self, key): ) ReadAttributeReq(self, key, ZIGATE_EP, EPout, "0300", listAttributes, ackIsDisabled=is_ack_tobe_disabled(self, key)) +def ReadAttributeRequest_0300_Color_Capabilities(self, key): + # Cluster 0x0300 - Color Control + self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0300_Color_Capabilities - Key: %s " % key, nwkid=key) + ListOfEp = getListOfEpForCluster(self, key, "0300") + for EPout in ListOfEp: + ReadAttributeReq(self, key, ZIGATE_EP, EPout, "0300", [ 0x400A], ackIsDisabled=is_ack_tobe_disabled(self, key)) + def ReadAttributeRequest_0400(self, key): self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0400 - Key: %s " % key, nwkid=key) diff --git a/Modules/tuya.py b/Modules/tuya.py index 90b1a585e..e799149fc 100644 --- a/Modules/tuya.py +++ b/Modules/tuya.py @@ -11,6 +11,7 @@ """ +import struct import time from datetime import datetime, timedelta @@ -115,16 +116,7 @@ def tuya_cmd_0x0000_0xf0(self, NwkId): # Seen at pairing of a WGH-JLCZ02 / TS011F and TS0201 and TS0601 (MOES BRT-100) payload = "11" + get_and_inc_ZCL_SQN(self, NwkId) + "fe" - raw_APS_request( - self, - NwkId, - '01', - "0000", - "0104", - payload, - zigate_ep=ZIGATE_EP, - ackIsDisabled=is_ack_tobe_disabled(self, NwkId), - ) + raw_APS_request( self, NwkId, '01', "0000", "0104", payload, zigate_ep=ZIGATE_EP, ackIsDisabled=is_ack_tobe_disabled(self, NwkId), ) self.log.logging("Tuya", "Debug", "tuya_cmd_0x0000_0xf0 - Nwkid: %s reset device Cmd: fe" % NwkId) @@ -1515,3 +1507,52 @@ def ts110e_switch_type( self, NwkId, EPout, mode): self.log.logging("Tuya", "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) + +def tuya_lighting_color_control( self, NwkId, ColorCapabilities=25): + # The ColorCapabilities attribute specifies the color capabilities of the device supporting the color control clus- + # ter, as illustrated in Table 5.8. If a bit is set to 1, the corresponding attributes and commands SHALL become + # mandatory. If a bit is set to 0, the corresponding attributes and commands need not be implemented. + + self.log.logging("Tuya", "Debug", "tuya_lighting_color_control - Color Capabilities %s" % ColorCapabilities, NwkId) + write_attribute( self, NwkId, ZIGATE_EP, "01", "0300", "0000", "00", "400a", "19", "%04x" %ColorCapabilities, ackIsDisabled=False ) + self.log.logging("Tuya", "Debug", "tuya_lighting_color_control - Color Capabilities %s completed" % ColorCapabilities, NwkId) + + +def tuya_color_control_rgbMode( self, NwkId, mode): + # Command 0xfe: Set mode (Tuya-specific command) + # Change the mode. + # 0: White light + # 1: Colored light + # 2: Scene + # 3: Music + # https://developer.tuya.com/en/docs/connect-subdevices-to-gateways/tuya-zigbee-lighting-access-standard?id=K9ik6zvod83fi#title-7-Color%20Control%20cluster + + self.log.logging("Tuya", "Debug", "tuya_color_control_rgbMode", NwkId) + sqn = get_and_inc_ZCL_SQN(self, NwkId) + payload = "11" + sqn + "f0" + mode + raw_APS_request(self, NwkId, "01", "0300", "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) + +def tuya_Move_To_Hue_Saturation( self, NwkId, EPout, hue, saturation, transition, level): + # Command 0x06 + self.log.logging("Tuya", "Debug", "tuya_Move_To_Hue_Saturation", NwkId) + + hue = "%02x" % hue + saturation = "%02x" % saturation + + sqn = get_and_inc_ZCL_SQN(self, NwkId) + payload = "11" + sqn + "06" + hue + saturation + "0000" + "%02x" %level + + raw_APS_request(self, NwkId, EPout, "0300", "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) + + +def tuya_Move_To_Hue_Saturation_Brightness( self, NwkId, epout, hue, saturation, brightness): + # Command 0xe1 + self.log.logging("Tuya", "Debug", "tuya_Move_To_Hue_Saturation_Brightness", NwkId) + + saturation = "%04x" % struct.unpack("H", struct.pack(">H", saturation))[0] + hue = "%04x" % struct.unpack("H", struct.pack(">H", hue, ))[0] + brightness = "%04x" % struct.unpack("H", struct.pack(">H", brightness))[0] + sqn = get_and_inc_ZCL_SQN(self, NwkId) + payload = "11" + sqn + "e1" + hue + saturation + brightness + + raw_APS_request(self, NwkId, epout, "0300", "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) diff --git a/Zigbee/zclCommands.py b/Zigbee/zclCommands.py index 28637ba78..d61f4eee0 100644 --- a/Zigbee/zclCommands.py +++ b/Zigbee/zclCommands.py @@ -36,6 +36,7 @@ zcl_raw_read_report_config_request, zcl_raw_remove_all_groups, zcl_raw_remove_group_member_ship, + zcl_raw_reset_device, zcl_raw_send_group_member_ship_identify, zcl_raw_window_covering, zcl_raw_write_attributeNoResponse) @@ -44,6 +45,11 @@ # Standard commands +def zcl_reset_device(self, nwkid, epin, epout): + self.log.logging("zdpCommand", "Debug", "zcl_reset_device %s %s %s" % (nwkid, epin, epout)) + if "ControllerInRawMode" in self.pluginconf.pluginConf and self.pluginconf.pluginConf["ControllerInRawMode"]: + return zcl_raw_reset_device(self, nwkid, epin, epout) + return send_zigatecmd_raw(self, "0050", "02" + nwkid + epin + epout) def zcl_read_attribute(self, nwkid, EpIn, EpOut, Cluster, direction, manufacturer_spec, manufacturer, lenAttr, Attr, ackIsDisabled=DEFAULT_ACK_MODE): self.log.logging("zclCommand", "Debug", "read_attribute %s %s %s %s %s %s %s %s %s" % (nwkid, EpIn, EpOut, Cluster, direction, manufacturer_spec, manufacturer, lenAttr, Attr)) diff --git a/Zigbee/zclRawCommands.py b/Zigbee/zclRawCommands.py index eec66c217..93d52486d 100644 --- a/Zigbee/zclRawCommands.py +++ b/Zigbee/zclRawCommands.py @@ -15,6 +15,34 @@ # General Command Frame +def zcl_raw_reset_device(self, nwkid, epin, epout): + """ + Sends a raw ZCL reset device command to a Zigbee device to reset it. + + The raw command contains the frame control, sequence number, command, + and payload required for the ZCL reset device command. It is sent directly + without additional ZCL framing. + + Args: + nwkid: The network ID of the device to reset. + epin: The endpoint ID to send the command from. + epout: The endpoint ID to send the command to. + + Returns: + The sequence number used in the command. + """ + + self.log.logging("zclCommand", "Debug", "zcl_raw_default_response %s" % nwkid) + frame_control_field = "%02x" %0b0001_0001 + cmd = "00" + cluster = "0000" + sqn = get_and_inc_ZCL_SQN(self, nwkid) + payload = frame_control_field + sqn + cmd + zcl_command_formated_logging( self, "Reset Device (Raw)", nwkid, epout, sqn, cluster) + raw_APS_request(self, nwkid, epout, cluster, "0104", payload, zigpyzqn=sqn, zigate_ep=epin, ackIsDisabled=False) + return sqn + + # Read Attributes Command def rawaps_read_attribute_req(self, nwkid, EpIn, EpOut, Cluster, direction, manufacturer_spec, manufacturer, Attr, ackIsDisabled=DEFAULT_ACK_MODE, groupaddrmode=False): self.log.logging("zclCommand", "Debug", "rawaps_read_attribute_req %s %s %s %s %s %s %s %s" % (nwkid, EpIn, EpOut, Cluster, direction, manufacturer_spec, manufacturer, Attr))