diff --git a/Classes/IAS.py b/Classes/IAS.py index 189c9cd25..5c7f63f57 100644 --- a/Classes/IAS.py +++ b/Classes/IAS.py @@ -72,7 +72,23 @@ STROBE_LEVEL = {"Low": 0x00, "Medium": 0x01} + SIRENE_MODE = ("both", "siren", "strobe", "stop") + +WARNING_DEVICE_MODES = { + "both": (0x01, 0x01), + "siren": (0x00, 0x01), + "stop": (0x00, 0x00), + "strobe": (0x01, 0x00) +} + +SIREN_MODES = { + "both": (STROBE_LEVEL["Low"], 0x02, 0x01), + "siren": (0x00, 0x00, 0x02), + "stop": (0x00, 0x00, 0x00), + "strobe": (STROBE_LEVEL["Low"], 0x01, 0x00) +} + strobe_mode = 0x00 @@ -309,14 +325,18 @@ def write_IAS_WD_Squawk(self, NwkId, ep, SquawkMode): zcl_ias_wd_command_squawk(self, ZIGATE_EP, ep, NwkId, squawk_mode, strobe, squawk_level, ackIsDisabled=False) def warningMode(self, NwkId, ep, mode="both", siren_level=0x01, warning_duration=0x01, strobe_duty=0x32, strobe_level=0x00): - + self.logging("Debug", f"warningMode {mode} {siren_level} {warning_duration} {strobe_duty} {strobe_level}") + if mode in ( "siren", "both") and "Param" in self.ListOfDevices[ NwkId ] and "sirenLevel" in self.ListOfDevices[ NwkId ]["Param"]: siren_level = self.ListOfDevices[ NwkId ]["Param"]["sirenLevel"] - if mode in ( "strobe", "both") and "Param" in self.ListOfDevices[ NwkId ] and "sirenLevel" in self.ListOfDevices[ NwkId ]["Param"]: + + if mode in ( "strobe", "both") and "Param" in self.ListOfDevices[ NwkId ] and "strobeDutyCycle" in self.ListOfDevices[ NwkId ]["Param"]: strobe_duty = self.ListOfDevices[ NwkId ]["Param"]["strobeDutyCycle"] strobe_mode, warning_mode, strobe_level, warning_duration = ias_sirene_mode( self, NwkId , mode , warning_duration) - self.logging("Debug", f"warningMode - Mode: {bin(warning_mode)}, Duration: {warning_duration}, Duty: {strobe_duty}, Level: {strobe_level}") + + self.logging("Debug", f"warningMode - Mode: {warning_mode}, Duration: {warning_duration}, Duty: {strobe_duty}, Level: {strobe_level}") + zcl_ias_wd_command_start_warning(self, ZIGATE_EP, ep, NwkId, warning_mode, strobe_mode, siren_level, warning_duration, strobe_duty, strobe_level, groupaddrmode=False, ackIsDisabled=False) def siren_both(self, NwkId, ep): @@ -358,42 +378,19 @@ def iaswd_develco_warning(self, NwkId, ep, sirenonoff): raw_APS_request(self, NwkId, ep, Cluster, "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) return sqn - def ias_sensitivity(self, nwkid, sensitivity): - - if sensitivity not in ( 0, 1, 2): - return - write_attribute(self, nwkid, ZIGATE_EP, "01", "0500", "0000", "00", "0013", "20", "%02x" %sensitivity, ackIsDisabled=False) - - def ias_sirene_mode( self, NwkId , mode, warning_duration ): + self.logging("Debug", f"ias_sirene_mode - {NwkId} Mode: {mode}, Duration: {warning_duration}") + strobe_mode = warning_mode = strobe_level = 0x00 + if self.ListOfDevices[NwkId]["Model"] == "WarningDevice": - if mode == "both": - strobe_mode = 0x01 - warning_mode = 0x01 - elif mode == "siren": - warning_mode = 0x01 - elif mode == "stop": - strobe_mode = 0x00 - warning_mode = 0x00 - elif mode == "strobe": - strobe_mode = 0x01 - warning_mode = 0x00 + strobe_mode, warning_mode = WARNING_DEVICE_MODES.get(mode, (0x00, 0x00)) + elif mode in SIRENE_MODE: - if mode == "both": - strobe_level = STROBE_LEVEL["Low"] - strobe_mode = 0x02 - warning_mode = 0x01 - elif mode == "siren": - warning_mode = 0x02 - elif mode == "stop": - strobe_mode = 0x00 - warning_mode = 0x00 - elif mode == "strobe": - strobe_level = STROBE_LEVEL["Low"] - strobe_mode = 0x01 - warning_mode = 0x00 + strobe_level, strobe_mode, warning_mode = SIREN_MODES.get(mode, (0x00, 0x00, 0x00)) + + self.logging("Debug", f"ias_sirene_mode - before checking param {NwkId} warning_mode: {warning_mode}, strobe_mode: {strobe_mode}, strobe_level: {strobe_level}") if "Param" in self.ListOfDevices[NwkId]: if "alarmDuration" in self.ListOfDevices[NwkId]["Param"]: warning_duration = int(self.ListOfDevices[NwkId]["Param"]["alarmDuration"]) @@ -405,7 +402,9 @@ def ias_sirene_mode( self, NwkId , mode, warning_duration ): warning_mode = int(self.ListOfDevices[NwkId]["Param"]["alarmSirenCode"]) if mode in ("strobe", "both") and "strobeLevel" in self.ListOfDevices[ NwkId ]["Param"]: - strobe_level = self.ListOfDevices[ NwkId ]["Param"]["strobeLevel"] + strobe_level = self.ListOfDevices[ NwkId ]["Param"]["strobeLevel"] + + self.logging("Debug", f"ias_sirene_mode - after checking param {NwkId} warning_mode: {warning_mode}, strobe_mode: {strobe_mode}, strobe_level: {strobe_level}, warning_duration: {warning_duration}") return strobe_mode, warning_mode, strobe_level, warning_duration diff --git a/Classes/LoggingManagement.py b/Classes/LoggingManagement.py index 9fe6b53f6..0ad2f14f7 100644 --- a/Classes/LoggingManagement.py +++ b/Classes/LoggingManagement.py @@ -56,9 +56,6 @@ def __init__(self, pluginconf, PluginHealth, HardwareID, ListOfDevices, permitTo configure_zigpy_zigate_loggers("warning") configure_zigpy_deconz_loggers("warning") - - - self.zigpy_login() start_logging_thread(self) diff --git a/Classes/PluginConf.py b/Classes/PluginConf.py index eaaf6d0f9..4445a83ab 100644 --- a/Classes/PluginConf.py +++ b/Classes/PluginConf.py @@ -293,6 +293,7 @@ "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 }, + "iasSettings": { "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 }, diff --git a/DevicesModules/custom_sonoff.py b/DevicesModules/custom_sonoff.py index 1ae4c3b63..533a510a2 100644 --- a/DevicesModules/custom_sonoff.py +++ b/DevicesModules/custom_sonoff.py @@ -11,13 +11,17 @@ # SPDX-License-Identifier: GPL-3.0 license from Modules.basicOutputs import write_attribute -from Modules.readAttributes import ReadAttributeRequest_0406_0022 +from Modules.tools import get_device_config_param from Modules.zigateConsts import ZIGATE_EP SONOFF_MAUFACTURER_NAME = "SONOFF" SONOFF_MANUFACTURER_ID = "1286" SONOFF_CLUSTER_ID = "fc11" SONOFF_ILLUMINATION_ATTRIBUTE = "2001" +SONOFF_MAX_TEMP = "0003" +SONOFF_MIN_TEMP = "0004" +SONOFF_MAX_HUMI = "0005" +SONOFF_MIN_HUMI = "0006" def is_sonoff_device(self, nwkid): @@ -33,7 +37,26 @@ 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_MANUFACTURER_ID, "01", "6000", "10", "%02x" %detection, ackIsDisabled=False) + +def sonoff_temp_humi_ranges(self, nwkid, value): + self.log.logging("Sonoff", "Debug", "sonoff_temp_humi_ranges - Nwkid: %s Mode: %s" %(nwkid, value)) + temp_max = get_device_config_param(self, nwkid, "SONOFF_TEMP_MAX") + temp_min = get_device_config_param(self, nwkid, "SONOFF_TEMP_MIN") + humi_max = get_device_config_param(self, nwkid, "SONOFF_HUMI_MAX") + humi_min = get_device_config_param(self, nwkid, "SONOFF_HUMI_MIN") + + write_attribute(self, nwkid, ZIGATE_EP, "01", SONOFF_CLUSTER_ID, SONOFF_MANUFACTURER_ID, "01", SONOFF_MAX_TEMP, "29", "%04x" %temp_max, ackIsDisabled=False) + write_attribute(self, nwkid, ZIGATE_EP, "01", SONOFF_CLUSTER_ID, SONOFF_MANUFACTURER_ID, "01", SONOFF_MIN_TEMP, "29", "%04x" %temp_min, ackIsDisabled=False) + write_attribute(self, nwkid, ZIGATE_EP, "01", SONOFF_CLUSTER_ID, SONOFF_MANUFACTURER_ID, "01", SONOFF_MAX_HUMI, "21", "%04x" %humi_max, ackIsDisabled=False) + write_attribute(self, nwkid, ZIGATE_EP, "01", SONOFF_CLUSTER_ID, SONOFF_MANUFACTURER_ID, "01", SONOFF_MIN_HUMI, "21", "%04x" %humi_min, ackIsDisabled=False) + + + SONOFF_DEVICE_PARAMETERS = { "SonOffTRVChildLock": sonoff_child_lock, "SonOffTRVWindowDectection": sonoff_open_window_detection, + "SONOFF_TEMP_MAX": sonoff_temp_humi_ranges, + "SONOFF_TEMP_MIN": sonoff_temp_humi_ranges, + "SONOFF_HUMI_MAX": sonoff_temp_humi_ranges, + "SONOFF_HUMI_MIN": sonoff_temp_humi_ranges } diff --git a/Modules/actuators.py b/Modules/actuators.py index 4c01fc867..4fd182545 100644 --- a/Modules/actuators.py +++ b/Modules/actuators.py @@ -235,6 +235,7 @@ def get_all_transition_mode( self, Nwkid): transitionHue = "%04x" % int(self.ListOfDevices[Nwkid]["Param"]["moveToHueSatu"]) return transitionMoveLevel , transitionRGB , transitionMoveLevel ,transitionHue , transitionTemp + def actuator_setcolor(self, nwkid, EPout, value, Color): Hue_List = json.loads(Color) @@ -287,12 +288,17 @@ def handle_color_mode_2(self, nwkid, EPout, Hue_List): # t is 0 > 255 TempKelvin = int(((255 - int(Hue_List["t"])) * (6500 - 1700) / 255) + 1700) TempMired = 1000000 // TempKelvin + + if get_deviceconf_parameter_value(self, self.ListOfDevices[nwkid]["Model"], "WSRangeForTradfri", return_default=False): + # Looks like Tradfri might have some limitation + self.log.logging( "Command", "Debug", "handle_color_mode_2 bring TempMired into the Tradfri Range 250,454", nwkid ) + TempMired = max(250, min(TempMired, 454)) + 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"]))) @@ -305,7 +311,8 @@ def handle_color_mode_3(self, nwkid, EPout, Hue_List): 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 ): # Gledopto GL_008 # Color: {"b":43,"cw":27,"g":255,"m":4,"r":44,"t":227,"ww":215} @@ -335,7 +342,8 @@ def handle_color_mode_4(self, nwkid, EPout, Hue_List ): 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, 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"]))) @@ -375,6 +383,7 @@ def handle_color_mode_tuya( self, nwkid, EPout, Hue_List, value): 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): if value is None: @@ -417,6 +426,7 @@ def decode_color_capabilities(capabilities_value): 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) diff --git a/Modules/domoCreate.py b/Modules/domoCreate.py index 87d19a2f1..d5ef3e6cc 100644 --- a/Modules/domoCreate.py +++ b/Modules/domoCreate.py @@ -301,14 +301,17 @@ def CreateDomoDevice(self, Devices, NWKID): t = update_widget_type_if_possible( self, NWKID, t) + if t in ("Aqara", "XCube"): + # We expect Only 1 Type, so after the creation of the 2 Widgets break + create_xcube_widgets(self, Devices, NWKID, DeviceID_IEEE, Ep, t) + break + if create_native_widget( self, Devices, NWKID, DeviceID_IEEE, Ep, t): continue if create_switch_selector_widget( self, Devices, NWKID, DeviceID_IEEE, Ep, t): continue - if t in ("Aqara", "XCube") and create_xcube_widgets(self, Devices, NWKID, DeviceID_IEEE, Ep, t): - continue # for Ep update_device_type( self, NWKID, GlobalType ) @@ -355,27 +358,31 @@ def create_xcube_widgets(self, Devices, NWKID, DeviceID_IEEE, Ep, t): # usage later on is based on that assumption # # Xiaomi Magic Cube + self.log.logging( "WidgetCreation", "Debug", f"create_xcube_widgets - Xiaomi Magic Cube {NWKID} {DeviceID_IEEE} {Ep} {t}") + self.ListOfDevices[NWKID]["Status"] = "inDB" # Create the XCube Widget Options = createSwitchSelector(self, 10, DeviceType=t, OffHidden=True, SelectorStyle=1) - unit = FreeUnit(self, Devices, nbunit_=2) # Look for 2 consecutive slots + unit = FreeUnit(self, Devices, DeviceID_IEEE, nbunit_=2) # Look for 2 consecutive slots myDev = Domoticz.Device( DeviceID=str(DeviceID_IEEE), Name=deviceName(self, NWKID, t, DeviceID_IEEE, Ep), Unit=unit, Type=244, Subtype=62, Switchtype=18, Options=Options, ) myDev.Create() ID = myDev.ID if myDev.ID == -1: self.ListOfDevices[NWKID]["Status"] = "failDB" - Domoticz.Error("Domoticz widget creation failed. %s" % (str(myDev))) + self.log.logging( "WidgetCreation", "Error", "Domoticz widget creation failed. %s" % (str(myDev))) else: + self.log.logging( "WidgetCreation", "Debug", f"create_xcube_widgets - widgetID {ID} for '{t}'") self.ListOfDevices[NWKID]["Ep"][Ep]["ClusterType"][str(ID)] = t # Create the Status (Text) Widget to report Rotation angle unit += 1 - myDev = Domoticz.Device( DeviceID=str(DeviceID_IEEE), Name=deviceName(self, NWKID, t, DeviceID_IEEE, Ep), Unit=unit, Type=243, Subtype=19, Switchtype=0, ) + myDev = Domoticz.Device( DeviceID=str(DeviceID_IEEE), Name=deviceName(self, NWKID, "Text", DeviceID_IEEE, Ep), Unit=unit, Type=243, Subtype=19, Switchtype=0, ) myDev.Create() ID = myDev.ID if myDev.ID == -1: - Domoticz.Error("Domoticz widget creation failed. %s" % (str(myDev))) + self.log.logging( "WidgetCreation", "Error", "Domoticz widget creation failed. %s" % (str(myDev))) else: + self.log.logging( "WidgetCreation", "Debug", f"create_xcube_widgets - widgetID {ID} for 'Text'") self.ListOfDevices[NWKID]["Ep"][Ep]["ClusterType"][str(ID)] = "Text" diff --git a/Modules/domoTools.py b/Modules/domoTools.py index 3833f9631..dd97b7db0 100644 --- a/Modules/domoTools.py +++ b/Modules/domoTools.py @@ -311,6 +311,7 @@ def UpdateDevice_v2(self, Devices, Unit, nValue, sValue, BatteryLvl, SignalLvl, domo_update_api(self, Devices, DeviceID_, Unit, nValue, sValue, SignalLevel=SignalLvl, BatteryLevel=BatteryLvl, TimedOut=0, Color=Color_,) if self.pluginconf.pluginConf["logDeviceUpdate"]: + self.log.logging( "Widget", "Log", "UpdateDevice - (%15s) %s:%s" % (Devices[Unit].Name, nValue, sValue)) domoticz_log_api( "UpdateDevice - (%15s) %s:%s" % (Devices[Unit].Name, nValue, sValue)) self.log.logging( "Widget", "Debug", "---> [Unit: %s] %s:%s:%s %s:%s %s (%15s)" % ( Unit, nValue, sValue, Color_, BatteryLvl, SignalLvl, ForceUpdate_, Devices[Unit].Name), self.IEEE2NWK[Devices[Unit].DeviceID], ) diff --git a/Modules/ias_settings.py b/Modules/ias_settings.py index 8d936f9f0..21649de3c 100644 --- a/Modules/ias_settings.py +++ b/Modules/ias_settings.py @@ -17,6 +17,7 @@ IAS_CLUSTER_ID = "0500" + ONOFF_CONFIG_SET = { "IAS_CIE_Address": ( "0010", "f0"), "ZoneID": ( "0011", "20"), @@ -24,8 +25,20 @@ "CurrentZoneSensitivityLevel": ( "0013", "20") } +IASWD_CLUSTER_ID = "0502" + +IASWD_CONFIG_SET = { + "IAS_WD_MAXIMUM_DURATION": ( "0000", "21"), +} + +def ias_CurrentZoneSensitivityLevel(self, nwkid, value): + self.log.logging( "iasSettings", "Debug", f"ias_CurrentZoneSensitivityLevel for {nwkid} - sensitivity: {value}", nwkid ) + ListOfEp = getListOfEpForCluster(self, nwkid, IAS_CLUSTER_ID) + for ep in ListOfEp: + ias_CurrentZoneSensitivityLevel_by_ep(self, nwkid, ep, value) -def ias_CurrentZoneSensitivityLevel(self, nwkid, ep, value): + +def ias_CurrentZoneSensitivityLevel_by_ep(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 @@ -33,7 +46,7 @@ def ias_CurrentZoneSensitivityLevel(self, nwkid, ep, value): # 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 ) + self.log.logging( "iasSettings", "Debug", f"ias_CurrentZoneSensitivityLevel for {nwkid}/{ep} - value: {value}", nwkid ) write_attribute( self, nwkid, @@ -48,6 +61,34 @@ def ias_CurrentZoneSensitivityLevel(self, nwkid, ep, value): ackIsDisabled=False, ) + +def ias_maximum_duration(self, nwkid, maximum_duration=60): + + self.log.logging( "iasSettings", "Debug", f"ias_maximum_duration for {nwkid} - max_duration: {maximum_duration}", nwkid ) + + ListOfEp = getListOfEpForCluster(self, nwkid, IAS_CLUSTER_ID) + for ep in ListOfEp: + ias_CurrentZoneSensitivityLevel_by_ep(self, nwkid, ep, maximum_duration) + + +def ias_maximum_duration_by_ep(self, nwkid, ep, maximum_duration=60): + self.log.logging( "iasSettings", "Debug", f"ias_maximum_duration_by_ep for {nwkid} - max_duration: {maximum_duration}", nwkid ) + + write_attribute( + self, + nwkid, + ZIGATE_EP, + ep, + IASWD_CLUSTER_ID, + "0000", + "00", + IASWD_CONFIG_SET[ "IAS_WD_MAXIMUM_DURATION"][0], + IASWD_CONFIG_SET[ "IAS_WD_MAXIMUM_DURATION"][1], + "%04x" %maximum_duration, + ackIsDisabled=False, ) + IAS_DEVICE_PARAMETERS = { - "CurrentZoneSensitivityLevel": ias_CurrentZoneSensitivityLevel -} + "CurrentZoneSensitivityLevel": ias_CurrentZoneSensitivityLevel, + "IASsensitivity": ias_CurrentZoneSensitivityLevel, + "SireneMaxAlarmDuration": ias_maximum_duration +} \ No newline at end of file diff --git a/Modules/occupancy_settings.py b/Modules/occupancy_settings.py index 7921cc25c..e68a01138 100644 --- a/Modules/occupancy_settings.py +++ b/Modules/occupancy_settings.py @@ -128,8 +128,8 @@ def Ultrasonic_occupied_to_unoccupied_delay(self, nwkid, ep, value): OCCUPANCY_CLUSTER_ID, "0000", "00", - PIR_CONFIG_SET[ "UltrasonicOccupiedToUnoccupiedDelay"][0], - PIR_CONFIG_SET[ "UltrasonicOccupiedToUnoccupiedDelay"][1], + ULTRASONIC_CONFIG_SET[ "UltrasonicOccupiedToUnoccupiedDelay"][0], + ULTRASONIC_CONFIG_SET[ "UltrasonicOccupiedToUnoccupiedDelay"][1], "%04x" %value, ackIsDisabled=False, ) @@ -157,8 +157,8 @@ def Ultrasonic_unoccupied_to_occupied_delay(self, nwkid, ep, value): OCCUPANCY_CLUSTER_ID, "0000", "00", - PIR_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedDelay"][0], - PIR_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedDelay"][1], + ULTRASONIC_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedDelay"][0], + ULTRASONIC_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedDelay"][1], "%04x" %value, ackIsDisabled=False, ) @@ -174,8 +174,8 @@ def Ultrasonic_unoccupied_to_occupied_threshold(self, nwkid, ep, value): OCCUPANCY_CLUSTER_ID, "0000", "00", - PIR_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedThreshold"][0], - PIR_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedThreshold"][1], + ULTRASONIC_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedThreshold"][0], + ULTRASONIC_CONFIG_SET[ "UltrasonicUnoccupiedToOccupiedThreshold"][1], "%02x" %value, ackIsDisabled=False, ) @@ -183,7 +183,7 @@ def Ultrasonic_unoccupied_to_occupied_threshold(self, nwkid, ep, value): 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] + target_attribute = ULTRASONIC_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) diff --git a/Modules/paramDevice.py b/Modules/paramDevice.py index f9bd5cb85..110cb4187 100644 --- a/Modules/paramDevice.py +++ b/Modules/paramDevice.py @@ -43,18 +43,6 @@ def Ballast_min_level(self, nwkid, min_level): ballast_Configuration_min_level(self, nwkid, min_level) -def ias_wd_sirene_max_alarm_dureation( self, nwkid, duration): - if self.iaszonemgt: - Epout = getEpForCluster(self, nwkid, "0502", strict=True) - - if Epout is not None and len(Epout) == 1: - self.iaszonemgt.IAS_WD_Maximum_duration( nwkid, Epout[0], duration) - -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, @@ -68,8 +56,6 @@ def ias_sensitivity(self, nwkid, sensitivity): "DanfossCovered": danfoss_covered, "DanfossTRVOrientation": danfoss_orientation, "DanfossViewDirection": danfoss_viewdirection, - "SireneMaxAlarmDuration": ias_wd_sirene_max_alarm_dureation, - "IASsensitivity": ias_sensitivity, } diff --git a/Modules/tools.py b/Modules/tools.py index f9920e1a0..5d6dd8e55 100644 --- a/Modules/tools.py +++ b/Modules/tools.py @@ -1446,6 +1446,7 @@ def how_many_devices(self): return routers, enddevices def get_deviceconf_parameter_value(self, model, attribute, return_default=None): + """ Retreive Configuration Attribute from Config file""" if model in ( '', {}): return return_default @@ -1670,6 +1671,7 @@ def is_domoticz_touch(self): def get_device_config_param( self, NwkId, config_parameter): + """ Retreive config_parameter from the Param section in Config or Device""" self.log.logging("Input", "Debug", "get_device_config_param: %s Config: %s" %( NwkId,config_parameter )) diff --git a/Modules/tuya.py b/Modules/tuya.py index 240b36aa7..714d2c9a5 100644 --- a/Modules/tuya.py +++ b/Modules/tuya.py @@ -1603,6 +1603,17 @@ def tuya_color_grandiant(self, NwkId, on_gradiant=None, off_gradiant=None): raw_APS_request(self, NwkId, epout, "0300", "0104", payload, zigpyzqn=sqn, zigate_ep=ZIGATE_EP, ackIsDisabled=False) +def ts0224_alarm_volume(self, nwkid, volume=0): + self.log.logging("tuyaSettings", "Debug", f"ts0224_alarm_volume {nwkid}, Volume: {volume}", nwkid) + + # Volume: 0: Mute, 50 High, 30 Medium, 10 Low + valid_values = [0, 10, 30, 50] + if volume not in valid_values: + volume = min(valid_values, key=lambda x: abs(x - volume)) + self.log.logging("tuyaSettings", "Debug", f"ts0224_alarm_volume {nwkid}, Volume: {volume} corrected as invalid", nwkid) + write_attribute( self, nwkid, ZIGATE_EP, "01", "0502", "0000", "00", "0002","20", "%02x" %volume, ackIsDisabled=False, ) + + TUYA_DEVICE_PARAMETERS = { "TuyaColorGradiantOnTime": tuya_color_grandiant, "TuyaColorGradiantOffTime": tuya_color_grandiant, @@ -1633,4 +1644,5 @@ def tuya_color_grandiant(self, NwkId, on_gradiant=None, off_gradiant=None): "TS110ELightType": ts110e_light_type, "TS110ESwitch01Type": ts110e_switch01_type, "TS110ESwitch02Type": ts110e_switch02_type, + "TuyaTS0224AlarmVolume": ts0224_alarm_volume } diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 561ea85d9..047e45c3d 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -18,6 +18,17 @@ Release Numbering - Odd numbers --> Stable/6 - Even numbers --> Beta/6 (dev branch) + +## March 2024 - stable7.1.010 ( 2024.03) + +- [Hardware] - define Temp and Humi Max,Min for Sonoff Temp/Humi +- [Issue] - Fix XCube/Aqara regression introduce in a previous release +- [Technical] - Define IAS settings +- [Technical] - Code for TS0224 Alarlm volume +- [Hardware] - Integrate Door Bell from Heiman +- [Hardware] - Take in consideration White Color temp mirred limitation and use a specific range + + ## March 2024 - stable7.1.009 (hotfix) - [Bug] - fix bugs generating error and preventing some Tuya features working diff --git a/Z4D_decoders/z4d_decoder_IAS.py b/Z4D_decoders/z4d_decoder_IAS.py index d41d928a0..088159b39 100644 --- a/Z4D_decoders/z4d_decoder_IAS.py +++ b/Z4D_decoders/z4d_decoder_IAS.py @@ -68,67 +68,69 @@ def Decode8400(self, Devices, MsgData, MsgLQI): def Decode8401(self, Devices, MsgData, MsgLQI): self.log.logging('Input', 'Debug', 'Decode8401 - Reception Zone status change notification: ' + MsgData) - + zone_status_fields =_extract_zone_status_info(self, MsgData) MsgSQN, MsgEp, MsgClusterId, MsgSrcAddrMode, MsgSrcAddr, MsgZoneStatus, MsgExtStatus, MsgZoneID, MsgDelay = zone_status_fields if zone_status_fields is None: error_message = f'Decode8401 - Reception Zone status change notification but incorrect Address Mode: {MsgSrcAddrMode} with MsgData {MsgData}' self.log.logging('Input', 'Error', error_message) return - + ias_dic = self.ListOfDevices[MsgSrcAddr].setdefault('Ep', {}).setdefault(MsgEp, {}).setdefault(MsgClusterId, {}) ias_dic.setdefault('0002', {}) lastSeenUpdate(self, Devices, NwkId=MsgSrcAddr) - + if MsgSrcAddr not in self.ListOfDevices: self.log.logging('Input', 'Error', 'Decode8401 - unknown IAS device %s from plugin' % MsgSrcAddr) if not zigpy_plugin_sanity_check(self, MsgSrcAddr): handle_unknow_device(self, MsgSrcAddr) return - + if 'Health' in self.ListOfDevices[MsgSrcAddr] and self.ListOfDevices[MsgSrcAddr]['Health'] not in ('Disabled',): self.ListOfDevices[MsgSrcAddr]['Health'] = 'Live' - + timeStamped(self, MsgSrcAddr, 33793) updSQN(self, MsgSrcAddr, MsgSQN) updLQI(self, MsgSrcAddr, MsgLQI) - - Model = '' - if 'Model' in self.ListOfDevices[MsgSrcAddr]: - Model = self.ListOfDevices[MsgSrcAddr]['Model'] - + + Model = self.ListOfDevices[MsgSrcAddr].get('Model', '') self.log.logging('Input', 'Debug', 'Decode8401 - MsgSQN: %s MsgSrcAddr: %s MsgEp:%s MsgClusterId: %s MsgZoneStatus: %s MsgExtStatus: %s MsgZoneID: %s MsgDelay: %s' % (MsgSQN, MsgSrcAddr, MsgEp, MsgClusterId, MsgZoneStatus, MsgExtStatus, MsgZoneID, MsgDelay), MsgSrcAddr) if Model == 'PST03A-v2.2.5': Decode8401_PST03Av225(self, Devices, MsgSrcAddr, MsgEp, Model, MsgZoneStatus) return - + status_bits = [int(MsgZoneStatus, 16) >> i & 1 for i in range(10)] alarm1, alarm2, tamper, battery, suprrprt, restrprt, trouble, acmain, test, battdef = status_bits _ensure_ep_cluster_structure(self, MsgSrcAddr, MsgEp, MsgClusterId) - + self.ListOfDevices[MsgSrcAddr]['Ep'][MsgEp]['0500']['0002'] = 'alarm1: %s, alarm2: %s, tamper: %s, battery: %s, Support Reporting: %s, restore Reporting: %s, trouble: %s, acmain: %s, test: %s, battdef: %s' % (alarm1, alarm2, tamper, battery, suprrprt, restrprt, trouble, acmain, test, battdef) self.log.logging('Input', 'Debug', 'IAS Zone for device:%s - %s' % (MsgSrcAddr, self.ListOfDevices[MsgSrcAddr]['Ep'][MsgEp]['0500']['0002']), MsgSrcAddr) self.log.logging('Input', 'Debug', 'Decode8401 MsgZoneStatus: %s ' % MsgZoneStatus[2:4], MsgSrcAddr) - - value = MsgZoneStatus[2:4] - + + heiman_door_bell_button = get_deviceconf_parameter_value(self, Model, "HeimanDoorBellButton", return_default=False) + self.log.logging('Input', 'Debug', 'HeimanDoorBellButton = %s' % heiman_door_bell_button) + + if heiman_door_bell_button: + self.log.logging('Input', 'Debug',f"Decode8401 HeimanDoorBellButton: {MsgSrcAddr} {MsgZoneStatus}", MsgSrcAddr) + if tamper: + MajDomoDevice(self, Devices, MsgSrcAddr, MsgEp, '0006', '01') + return + motion_via_IAS_alarm = get_device_config_param(self, MsgSrcAddr, 'MotionViaIASAlarm1') - self.log.logging('Input', 'Debug', 'MotionViaIASAlarm1 = %s' % motion_via_IAS_alarm) - + ias_alarm1_2_merged = get_deviceconf_parameter_value(self, Model, 'IASAlarmMerge', return_default=None) - self.log.logging('Input', 'Debug', 'IASAlarmMerge = %s' % ias_alarm1_2_merged) - + if ias_alarm1_2_merged: self.log.logging('Input', 'Debug', 'IASAlarmMerge alarm1 %s alarm2 %s' % (alarm1, alarm2)) combined_alarm = alarm2 << 1 | alarm1 self.log.logging('Input', 'Debug', 'IASAlarmMerge combined value = %02d' % combined_alarm) MajDomoDevice(self, Devices, MsgSrcAddr, MsgEp, '0006', '%02d' % combined_alarm) - + elif motion_via_IAS_alarm is not None and motion_via_IAS_alarm == 1: self.log.logging('Input', 'Debug', 'Motion detected sending to MajDomo %s/%s %s' % (MsgSrcAddr, MsgEp, alarm1 or alarm2)) MajDomoDevice(self, Devices, MsgSrcAddr, MsgEp, '0406', '%02d' % (alarm1 or alarm2)) @@ -158,6 +160,7 @@ def Decode8401(self, Devices, MsgData, MsgLQI): _update_ias_zone_status(self, MsgSrcAddr, MsgEp, MsgZoneStatus) + def _extract_zone_status_info(self, msg_data): MsgSQN = msg_data[:2] MsgEp = msg_data[2:4] @@ -210,6 +213,7 @@ def _update_ias_zone_status(self, msg_src_addr, msg_ep, msg_zone_status): zone_status[status_name] = status_value zone_status['GlobalInfos'] = self.ListOfDevices.get(msg_src_addr, {}).get('Ep', {}).get(msg_ep, {}).get('0500', {}).get('0002', {}) + zone_status['zoneStatus'] = msg_zone_status zone_status['TimeStamp'] = int(time.time()) @@ -240,4 +244,4 @@ def Decode8401_PST03Av225(self, Devices, MsgSrcAddr, MsgEp, Model, MsgZoneStatus else: self.log.logging('Input', 'Debug', 'Decode8401 - PST03A-v2.2.5, unknown EndPoint: ' + MsgEp, MsgSrcAddr) - return \ No newline at end of file + return diff --git a/Z4D_decoders/z4d_decoder_Remotes.py b/Z4D_decoders/z4d_decoder_Remotes.py index bfaef22cd..e1f7d2d55 100644 --- a/Z4D_decoders/z4d_decoder_Remotes.py +++ b/Z4D_decoders/z4d_decoder_Remotes.py @@ -370,14 +370,14 @@ def Decode80A7(self, Devices, MsgData, MsgLQI): def scene_mapping(self, Devices, remote_scene_mapping_data, MsgSrcAddr, MsgEP, MsgClusterId, MsgCmd=None, unknown_=None, MsgDirection=None): """Implementation based on Device JSON configuration.""" - self.log.logging('Input', 'Log', f"scene_mapping {MsgSrcAddr} {MsgEP} {MsgClusterId} {MsgCmd} {unknown_} {MsgDirection} {remote_scene_mapping_data}") + self.log.logging('Input', 'Debug', f"scene_mapping {MsgSrcAddr} {MsgEP} {MsgClusterId} {MsgCmd} {unknown_} {MsgDirection} {remote_scene_mapping_data}") matching_criteria = f"{MsgCmd}_{unknown_}_{MsgDirection}" if MsgDirection is not None else f"{MsgCmd}_{unknown_}" cluster_mapping = remote_scene_mapping_data.get(MsgClusterId, {}) device_mapping = cluster_mapping.get(matching_criteria, None) - self.log.logging('Input', 'Log', f" mapping found ( {MsgCmd} {unknown_} {MsgDirection}) -> {device_mapping}") + self.log.logging('Input', 'Debug', f" mapping found ( {MsgCmd} {unknown_} {MsgDirection}) -> {device_mapping}") if device_mapping: MajDomoDevice(self, Devices, MsgSrcAddr, MsgEP, MsgClusterId, device_mapping) @@ -386,6 +386,6 @@ def scene_mapping(self, Devices, remote_scene_mapping_data, MsgSrcAddr, MsgEP, M def missing_scene_mapping(self, NwkId, Ep, ClusterId, model, Data, Cmd, Unknow, Direction): self.log.logging( 'Input', 'Log', f'Device {NwkId} with model {model} requires a scene mapping entry in the config file - msgdata: {Data[8:]}') - self.log.logging('Input', 'Log', f'Device "REMOTE_SCENE_MAPPING": {{ "{ClusterId}": {{ "{Cmd}_{Unknow}_{Direction}": }} }}') + self.log.logging( 'Input', 'Log', f'Device "REMOTE_SCENE_MAPPING": {{ "{ClusterId}": {{ "{Cmd}_{Unknow}_{Direction}": }} }}')