From cfd7f2667074984914a453a2d7e880febc025ec3 Mon Sep 17 00:00:00 2001 From: Nikita Uvarov Date: Tue, 29 Oct 2024 22:10:45 +0100 Subject: [PATCH 1/2] Add Schneider Electric Thermostats --- zhaquirks/schneiderelectric/thermostat.py | 576 ++++++++++++++++++++++ 1 file changed, 576 insertions(+) create mode 100644 zhaquirks/schneiderelectric/thermostat.py diff --git a/zhaquirks/schneiderelectric/thermostat.py b/zhaquirks/schneiderelectric/thermostat.py new file mode 100644 index 0000000000..378f7c86cd --- /dev/null +++ b/zhaquirks/schneiderelectric/thermostat.py @@ -0,0 +1,576 @@ +"""Schneider Electric thermostats quirks.""" + +from typing import Final + +from zigpy.quirks import CustomCluster +from zigpy.quirks.v2 import QuirkBuilder +import zigpy.types as t +from zigpy.zcl.clusters.hvac import SystemMode, Thermostat, UserInterface +from zigpy.zcl.clusters.measurement import TemperatureMeasurement +from zigpy.zcl.clusters.smartenergy import Metering +from zigpy.zcl.foundation import ZCLAttributeDef, ZCLCommandDef + +from zhaquirks.schneiderelectric import SE_MANUF_NAME, SEBasic + + +class SETemperatureSensorType(t.enum8): + """Temperature sensor connected to thermostat.""" + + Sensor2kOhm = 0x01 + Sensor10kOhm = 0x02 + Sensor12kOhm = 0x03 + Sensor15kOhm = 0x04 + Sensor33kOhm = 0x05 + Sensor47kOhm = 0x06 + SensorAbsent = 0xFF + + +class SEControlStatus(t.enum8): + """Control status of the thermostat.""" + + # Operating normally + NormalOperation = 0x00 + + # No temperature value is available. The output is using Fallback rules + NoTemperature = 0x20 + + # Output has been forced using a remote override + RemoteDemandOverride = 0x40 + + # Demand is constrained by Window Open rules + WindowOpen = 0x41 + + # Demand forced by "emergency" button + LocalForceOn = 0x61 + + # The output is being driven as part of a maintenance operation + Maintenance = 0x82 + + # Output held on, or off, despite a change in demand due to minimum times + OutputTemporalLimit = 0x83 + + # The output has been placed into a safe state due to a sensor failure + SensorFault = 0x84 + + +class SELocalTemperatureSourceSelect(t.enum8): + """Local temperature source select.""" + + # Internal temperature sensor + Ambient = 0x02 + + # External temperature sensor + External = 0x03 + + +class SEControlType(t.enum8): + """Control type of the thermostat.""" + + OnOff = 0x00 + PI = 0x01 + NoControl = 0xFF + + +class SEHeatCoolInputMode(t.enum8): + """Heat/Cool input mode.""" + + CoolingWhenActive = 0x00 + HeatingWhenActive = 0x01 + NotConnected = 0xFF + + +class SEThermostatApplication(t.enum8): + """Thermostat application.""" + + OccupiedSpace = 0x00 + Floor = 0x01 + NotKnown = 0xFF + + +class SEHeatingFuel(t.enum8): + """Heating fuel.""" + + Electricity = 0x00 + Gas = 0x01 + Oil = 0x02 + SolidFuel = 0x03 + Solar = 0x04 + ComunityHeating = 0x05 + HeatPump = 0x06 + NotSpecified = 0xFF + + +class SEHeatTransferMedium(t.enum8): + """Heat transfer medium.""" + + Nothing = 0x00 + Hydronic = 0x01 + Air = 0x02 + + +class SEHeatingEmitterType(t.enum8): + """Heating emitter type.""" + + Direct = 0x00 + Radiator = 0x01 + FanAssistedRadiator = 0x02 + RadiantPanel = 0x03 + Floor = 0x04 + NotSpecified = 0xFF + + +class SEDemandOverrideType(t.enum8): + """Demand override type.""" + + NoDemandOverride = 0x00 + CoolingDemandOverride = 0x03 + HeatingDemandOverride = 0x04 + + +class SESetpointChangeReason(t.enum8): + """Setpoint change reason.""" + + SetpointAdjustment = 0x00 + BoostUsed = 0x01 + BoostCancel = 0x02 + SystemModeChange = 0x03 + AwayStart = 0x04 + AwayEnd = 0x05 + + +class SEAffectedSetpoint(t.enum8): + """Affected setpoint.""" + + Nothing = 0x00 + OccupiedHeating = 0x01 + OccupiedCooling = 0x02 + UnoccupiedHeating = 0x03 + UnoccupiedCooling = 0x04 + + +class SECoolingOutputMode(t.enum8): + """Cooling output mode.""" + + Disabled = 0x00 + Relay = 0x01 + RelayNC = 0x04 + + +class SEHeatingOutputMode(t.enum8): + """Heating output mode.""" + + Disabled = 0x00 + Relay = 0x01 + OpenTherm = 0x02 + FilPilote = 0x03 + RelayNC = 0x04 + + +class SEMetering(CustomCluster, Metering): + """Schneider Electric Metering cluster.""" + + class AttributeDefs(Metering.AttributeDefs): + """Schneider Electric Metering cluster attributes.""" + + # This attribute specifies the demand of a switched load when it is energised + se_fixed_load_demand: Final = ZCLAttributeDef( + id=0x4510, + type=t.uint24_t, + access="rw", + is_manufacturer_specific=True, + ) + + +class SETemperatureMeasurement(CustomCluster, TemperatureMeasurement): + """Schneider Electric Temperature Measurement cluster.""" + + class AttributeDefs(TemperatureMeasurement.AttributeDefs): + """Schneider Electric Temperature Measurement cluster attributes.""" + + se_sensor_correction: Final = ZCLAttributeDef( + id=0xE020, + type=t.int16s, + access="rw", + is_manufacturer_specific=True, + ) + + +class SETemperatureMeasurementExternal(SETemperatureMeasurement): + """Schneider Electric Temperature Measurement cluster for external (floor) temperature input.""" + + class AttributeDefs(SETemperatureMeasurement.AttributeDefs): + """Schneider Electric Temperature Measurement cluster attributes.""" + + se_temperature_sensor_type: Final = ZCLAttributeDef( + id=0xE021, + type=SETemperatureSensorType, + access="rw", + is_manufacturer_specific=True, + ) + + +class SEThermostat(CustomCluster, Thermostat): + """Schneider Electric manufacturer specific Thermostat cluster.""" + + class ClientCommandDefs(Thermostat.ClientCommandDefs): + """Client command definitions.""" + + se_pi_demand_override: Final = ZCLCommandDef( + id=0xF1, + schema={ + "type": SEDemandOverrideType, + "demand": t.uint8_t, + "duration": t.uint8_t, + }, + is_manufacturer_specific=True, + ) + + class ServerCommandDefs(Thermostat.ServerCommandDefs): + """Server command definitions.""" + + se_local_setpoint_change_notification_command: Final = ZCLCommandDef( + id=0x91, + schema={ + "reason": SESetpointChangeReason, + "system_mode": SystemMode, + "affected_setpoint": SEAffectedSetpoint, + "setpoint_value": t.int16s, + "duration": t.uint16_t, + }, + is_manufacturer_specific=True, + ) + + class AttributeDefs(Thermostat.AttributeDefs): + """Attribute definitions.""" + + se_open_window_detection_status: Final = ZCLAttributeDef( + id=0xE012, + type=t.Bool, + access="r", + is_manufacturer_specific=True, + ) + se_open_window_detection_threshold: Final = ZCLAttributeDef( + id=0xE013, + type=t.uint8_t, + access="rw", + is_manufacturer_specific=True, + ) + se_open_window_event_duration: Final = ZCLAttributeDef( + id=0xE014, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_open_window_detection_guard_period: Final = ZCLAttributeDef( + id=0xE015, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_fallback_timeout: Final = ZCLAttributeDef( + id=0xE200, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_boost_amount: Final = ZCLAttributeDef( + id=0xE210, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_control_status: Final = ZCLAttributeDef( + id=0xE211, + type=SEControlStatus, + access="r", + is_manufacturer_specific=True, + ) + se_local_temperature_souce_select: Final = ZCLAttributeDef( + id=0xE212, + type=SELocalTemperatureSourceSelect, + access="rw", + is_manufacturer_specific=True, + ) + se_control_type: Final = ZCLAttributeDef( + id=0xE213, + type=SEControlType, + access="rw", + is_manufacturer_specific=True, + ) + se_heat_cool_input_mode: Final = ZCLAttributeDef( + id=0xE214, + type=SEHeatCoolInputMode, + access="rw", + is_manufacturer_specific=True, + ) + se_unoccupied_tracking_offset: Final = ZCLAttributeDef( + id=0xE215, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_thermostat_application: Final = ZCLAttributeDef( + id=0xE216, + type=SEThermostatApplication, + access="rw", + is_manufacturer_specific=True, + ) + se_heating_fuel: Final = ZCLAttributeDef( + id=0xE217, + type=SEHeatingFuel, + access="rw", + is_manufacturer_specific=True, + ) + se_heat_transfer_medium: Final = ZCLAttributeDef( + id=0xE218, + type=SEHeatTransferMedium, + access="rw", + is_manufacturer_specific=True, + ) + se_heating_emitter_type: Final = ZCLAttributeDef( + id=0xE218, + type=SEHeatingEmitterType, + access="rw", + is_manufacturer_specific=True, + ) + + +class SEUserInterface(CustomCluster, UserInterface): + """Schneider Electric Thermostat User Interface cluster.""" + + class AttributeDefs(UserInterface.AttributeDefs): + """Attribute definitions.""" + + se_brightness: Final = ZCLAttributeDef( + id=0xE000, + type=t.uint8_t, + access="rw", + is_manufacturer_specific=True, + ) + se_inactive_brightness: Final = ZCLAttributeDef( + id=0xE001, + type=t.uint8_t, + access="rw", + is_manufacturer_specific=True, + ) + se_activity_timeout: Final = ZCLAttributeDef( + id=0xE002, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + + +class SECycleTime(CustomCluster): + """Schneider Electric Cycle Time cluster.""" + + cluster_id = 0xFF16 + name = "SECycleTime" + + class AttributeDefs(CustomCluster.AttributeDefs): + """Attribute definitions.""" + + se_demand_percentage: Final = ZCLAttributeDef( + id=0x0000, + type=t.uint8_t, + access="r", + is_manufacturer_specific=True, + ) + se_cycle_time: Final = ZCLAttributeDef( + id=0x0010, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_min_cycle_time: Final = ZCLAttributeDef( + id=0x0011, + type=t.uint16_t, + access="r", + is_manufacturer_specific=True, + ) + se_max_cycle_time: Final = ZCLAttributeDef( + id=0x0012, + type=t.uint16_t, + access="r", + is_manufacturer_specific=True, + ) + se_status_flags: Final = ZCLAttributeDef( + id=0x0020, + type=t.bitmap8, + access="r", + is_manufacturer_specific=True, + ) + se_cluster_revision: Final = ZCLAttributeDef( + id=0xFFFD, + type=t.uint16_t, + access="r", + is_manufacturer_specific=True, + ) + + +class SEHeatingCoolingOutput(CustomCluster): + """Schneider Electric Heating Cooling Output cluster.""" + + cluster_id = 0xFF23 + name = "SEHeatingCoolingOutput" + + class AttributeDefs(CustomCluster.AttributeDefs): + """Attribute definitions.""" + + se_measured_temperature: Final = ZCLAttributeDef( + id=0x0000, + type=t.int16s, + access="r", + is_manufacturer_specific=True, + ) + se_abs_min_heat_temperature_limit: Final = ZCLAttributeDef( + id=0x0003, + type=t.int16s, + access="r", + is_manufacturer_specific=True, + ) + se_abs_max_heat_temperature_limit: Final = ZCLAttributeDef( + id=0x0004, + type=t.int16s, + access="r", + is_manufacturer_specific=True, + ) + se_abs_min_cool_temperature_limit: Final = ZCLAttributeDef( + id=0x0005, + type=t.int16s, + access="r", + is_manufacturer_specific=True, + ) + se_abs_max_cool_temperature_limit: Final = ZCLAttributeDef( + id=0x0006, + type=t.int16s, + access="r", + is_manufacturer_specific=True, + ) + se_min_heat_temperature_limit: Final = ZCLAttributeDef( + id=0x0015, + type=t.int16s, + access="rw", + is_manufacturer_specific=True, + ) + se_max_heat_temperature_limit: Final = ZCLAttributeDef( + id=0x0016, + type=t.int16s, + access="rw", + is_manufacturer_specific=True, + ) + se_min_cool_temperature_limit: Final = ZCLAttributeDef( + id=0x0017, + type=t.int16s, + access="rw", + is_manufacturer_specific=True, + ) + se_max_cool_temperature_limit: Final = ZCLAttributeDef( + id=0x0018, + type=t.int16s, + access="rw", + is_manufacturer_specific=True, + ) + se_heat_temperature_high_limit: Final = ZCLAttributeDef( + id=0x0020, + type=t.int16s, + access="rw", + is_manufacturer_specific=True, + ) + se_heat_temperature_low_limit: Final = ZCLAttributeDef( + id=0x0021, + type=t.int16s, + access="rw", + is_manufacturer_specific=True, + ) + se_cool_temperature_high_limit: Final = ZCLAttributeDef( + id=0x0022, + type=t.int16s, + access="rw", + is_manufacturer_specific=True, + ) + se_cool_temperature_low_limit: Final = ZCLAttributeDef( + id=0x0023, + type=t.int16s, + access="rw", + is_manufacturer_specific=True, + ) + se_cooling_output_mode: Final = ZCLAttributeDef( + id=0x0030, + type=SECoolingOutputMode, + access="rw", + is_manufacturer_specific=True, + ) + se_heating_output_mode: Final = ZCLAttributeDef( + id=0x0031, + type=SEHeatingOutputMode, + access="rw", + is_manufacturer_specific=True, + ) + se_maximum_idle_time: Final = ZCLAttributeDef( + id=0x0041, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_anti_idle_exercise_time: Final = ZCLAttributeDef( + id=0x0042, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_preffered_exercise_time: Final = ZCLAttributeDef( + id=0x0043, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_min_off_time: Final = ZCLAttributeDef( + id=0x0044, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_min_on_time: Final = ZCLAttributeDef( + id=0x0045, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_max_overall_duty_cycle: Final = ZCLAttributeDef( + id=0xE207, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_overall_duty_cycle_period: Final = ZCLAttributeDef( + id=0xE208, + type=t.uint16_t, + access="rw", + is_manufacturer_specific=True, + ) + se_cluster_revision: Final = ZCLAttributeDef( + id=0xFFFD, + type=t.uint16_t, + access="r", + is_manufacturer_specific=True, + ) + + +( + QuirkBuilder(SE_MANUF_NAME, "EKO07259") + .applies_to(SE_MANUF_NAME, "WDE002497") + .applies_to(SE_MANUF_NAME, "WDE011680") + .replaces(SEBasic) + .replaces(SEBasic, endpoint_id=2) + .replaces(SEBasic, endpoint_id=3) + .replaces(SEBasic, endpoint_id=5) + .replaces(SEThermostat) + .replaces(SEUserInterface) + .replaces(SETemperatureMeasurement, endpoint_id=2) + .replaces(SETemperatureMeasurementExternal, endpoint_id=3) + .replaces(SEMetering, endpoint_id=5) + .adds(SECycleTime) + .adds(SEHeatingCoolingOutput) + .add_to_registry() +) From 658b724972c93aaacd556e8aa53efba47d0b002b Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Sun, 24 Nov 2024 23:31:45 +0100 Subject: [PATCH 2/2] Use `replaces` instead of `adds` --- zhaquirks/schneiderelectric/thermostat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zhaquirks/schneiderelectric/thermostat.py b/zhaquirks/schneiderelectric/thermostat.py index 378f7c86cd..537c9e8705 100644 --- a/zhaquirks/schneiderelectric/thermostat.py +++ b/zhaquirks/schneiderelectric/thermostat.py @@ -570,7 +570,7 @@ class AttributeDefs(CustomCluster.AttributeDefs): .replaces(SETemperatureMeasurement, endpoint_id=2) .replaces(SETemperatureMeasurementExternal, endpoint_id=3) .replaces(SEMetering, endpoint_id=5) - .adds(SECycleTime) - .adds(SEHeatingCoolingOutput) + .replaces(SECycleTime) + .replaces(SEHeatingCoolingOutput) .add_to_registry() )