Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up Danfoss TRV custom attributes definitions and commands #2150

Merged
merged 93 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
da24506
Danfoss thermostat: Fixup attributes access, types, names and remove …
Caius-Bonus Jan 29, 2023
7d914a7
Danfoss thermostat: remove dead import
Caius-Bonus Jan 29, 2023
b904108
formatted using black
Caius-Bonus Jan 29, 2023
3e745ad
danfoss thermostat: map System Mode (aka Operation Mode or HVAC mode)…
Caius-Bonus Jan 29, 2023
e9282b3
fix unused assignment
Caius-Bonus Jan 29, 2023
f63b21f
danfoss thermostat: fix flake8 issues
Caius-Bonus Jan 29, 2023
bf7d911
danfoss thermostat: use absolute import
Caius-Bonus Feb 1, 2023
4009564
Danfoss thermostat: add trv_operation_mode, occupied_heating_setpoint…
Caius-Bonus Feb 4, 2023
6ebf132
Merge branch 'dev' into dev
Caius-Bonus Feb 4, 2023
4b227d1
reverse automatic edit in file I didn't want to touch
Caius-Bonus Feb 4, 2023
297e0b9
Merge branch 'dev' into dev
Caius-Bonus Aug 11, 2023
78e9047
fix errors
Caius-Bonus Aug 11, 2023
28208e8
fix pre-commit
Caius-Bonus Aug 11, 2023
dcee9be
fix some accidental misuse of identifiers, remove magic strings and n…
Caius-Bonus Aug 11, 2023
14954c9
write tests
Caius-Bonus Aug 12, 2023
d54966c
Merge branch 'zigpy:dev' into dev
Caius-Bonus Sep 2, 2023
7c3017a
refactoring
Caius-Bonus Sep 2, 2023
bcf41d7
remove scheduled setpoint
Caius-Bonus Sep 2, 2023
f70c7de
comment for programming operation mode
Caius-Bonus Sep 2, 2023
a1dc598
remove read_fakeattr and get_result_index
Caius-Bonus Sep 2, 2023
7faa857
fix tests
Caius-Bonus Sep 2, 2023
160592a
comment fix
Caius-Bonus Sep 2, 2023
b2c73b2
increase coverage
Caius-Bonus Sep 2, 2023
279f218
Merge branch 'dev' into dev
Caius-Bonus Sep 13, 2023
a111a63
change cluster ids
Caius-Bonus Sep 13, 2023
0a1e32c
remove custom clusters
Caius-Bonus Sep 19, 2023
2378e60
Merge branch 'dev' into dev
Caius-Bonus Sep 23, 2023
e40da88
move constants out of init file
Caius-Bonus Sep 29, 2023
7dfccd8
fix code quality
Caius-Bonus Sep 29, 2023
bce3c3c
Merge pull request #1 from Caius-Bonus/RemoveCustomClusters
Caius-Bonus Sep 29, 2023
4442b2b
Merge branch 'dev' into dev
Caius-Bonus Sep 29, 2023
b138169
added time sync
Caius-Bonus Oct 2, 2023
aa9694a
move attrs into new datastructure
Caius-Bonus Oct 2, 2023
54a3ef5
refactor comments
Caius-Bonus Oct 2, 2023
69ce96d
fix non-working standard clusters
Caius-Bonus Oct 2, 2023
3089731
style
Caius-Bonus Oct 2, 2023
b81c8f8
add tests
Caius-Bonus Oct 2, 2023
f19e739
remove some lines
Caius-Bonus Oct 2, 2023
6186216
add tests
Caius-Bonus Oct 2, 2023
7e8c74f
styl
Caius-Bonus Oct 2, 2023
9470e40
disallow systemmode=off
Caius-Bonus Oct 2, 2023
188dc17
fix test
Caius-Bonus Oct 2, 2023
337ec1e
remove non-standard programing_oper_mode
Caius-Bonus Oct 19, 2023
c48cd08
align with better cluster documentation
Caius-Bonus Oct 19, 2023
b798074
deviate from documentation, because it works anyway
Caius-Bonus Oct 19, 2023
7a8f8e0
remove accidentally comitted change
Caius-Bonus Oct 19, 2023
e2d5aae
remove unused enum
Caius-Bonus Oct 19, 2023
0102f42
according to the documentation, this should be a new valid danfoss trv
Caius-Bonus Oct 21, 2023
2232b55
Revert "according to the documentation, this should be a new valid da…
Caius-Bonus Oct 24, 2023
bc5568c
add quirk_id
Caius-Bonus Oct 26, 2023
0ccc1d5
Merge branch 'dev' into dev
Caius-Bonus Oct 26, 2023
24edf21
move definition to quirk_ids.py
Caius-Bonus Oct 26, 2023
216527a
black
Caius-Bonus Oct 26, 2023
71cbea2
comment
Caius-Bonus Oct 26, 2023
fc79193
make combine_results more robust
Caius-Bonus Oct 27, 2023
73111fc
reduce code duplication and don't send unnecessary requests
Caius-Bonus Oct 27, 2023
2bdb1dd
code style
Caius-Bonus Oct 27, 2023
4c2a789
comment
Caius-Bonus Oct 27, 2023
cc35dab
change list to List type for compatibility reasons
Caius-Bonus Oct 27, 2023
663c135
remove variable arg Callable for compatibility reasons
Caius-Bonus Oct 27, 2023
23654e7
Merge branch 'dev' into FixDanfossThermostat
Caius-Bonus Oct 31, 2023
2d0862e
black
Caius-Bonus Oct 31, 2023
e8970b2
improve quirk id to ward against future products
Caius-Bonus Nov 2, 2023
169a562
fix variable name
Caius-Bonus Nov 2, 2023
c54626d
use more standard notation
Caius-Bonus Jan 18, 2024
b5e4922
but do use custom clusters
Caius-Bonus Jan 18, 2024
d70cf14
use zcl constants from shared library
Caius-Bonus Jan 25, 2024
e287e24
docstring
Caius-Bonus Jan 30, 2024
08abd4b
define enums in quirk and shorten AttributeDefs usage
Caius-Bonus Feb 4, 2024
5d04d09
capitals and dots in docstrings
Caius-Bonus Feb 4, 2024
e0180a1
documentation
Caius-Bonus Feb 11, 2024
6b43203
fix docstring
Caius-Bonus Feb 13, 2024
fc1646e
simnplify and add enum value to excersice day of the week
Caius-Bonus Feb 13, 2024
d0c640f
add bitmaps and reorder attributes
Caius-Bonus Feb 13, 2024
bd56e0c
change captica,
Caius-Bonus Feb 13, 2024
9bb3d04
Merge branch 'dev' into FixDanfossThermostat
Caius-Bonus Feb 13, 2024
49a4556
correct capitalization for enums and bitmaps
Caius-Bonus Feb 13, 2024
a78b102
comments
Caius-Bonus Feb 13, 2024
4c21b4a
Merge branch 'dev' into FixDanfossThermostat
Caius-Bonus Feb 21, 2024
7ab8a7f
test failure and success correctly
Caius-Bonus Feb 21, 2024
1fcc763
now correclty
Caius-Bonus Feb 21, 2024
77020d4
remove accidentally added line
Caius-Bonus Feb 23, 2024
6013713
Merge branch 'dev' into FixDanfossThermostat
Caius-Bonus Feb 23, 2024
f3b2f58
swap
Caius-Bonus Feb 23, 2024
72f3e77
Merge branch 'dev' into FixDanfossThermostat
Caius-Bonus Apr 9, 2024
c5cc05c
suggestions
Caius-Bonus Apr 9, 2024
2f84b33
ruffle
Caius-Bonus Apr 9, 2024
6d8f204
Update tests/test_danfoss.py
Caius-Bonus Apr 9, 2024
c29ae99
Update tests/test_danfoss.py
Caius-Bonus Apr 9, 2024
d37c214
suggestions
Caius-Bonus Apr 9, 2024
94b6112
fix test
Caius-Bonus Apr 9, 2024
2684a35
Remove empty lines
TheJulianJES Apr 9, 2024
0aa4acf
Merge branch 'dev' into FixDanfossThermostat
TheJulianJES Apr 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion zhaquirks/danfoss/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Module for Danfoss quirks implementations."""
DANFOSS = "Danfoss"
D5X84YU = "D5X84YU"
HIVE = DANFOSS
POPP = "D5X84YU"
257 changes: 198 additions & 59 deletions zhaquirks/danfoss/thermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

manufacturer specific attributes to control displaying and specific configuration.
"""

import zigpy.profiles.zha as zha_p
from zigpy.quirks import CustomCluster, CustomDevice
import zigpy.types as t
Expand All @@ -17,6 +16,7 @@
)
from zigpy.zcl.clusters.homeautomation import Diagnostic
from zigpy.zcl.clusters.hvac import Thermostat, UserInterface
from zigpy.zcl.clusters.manufacturer_specific import ManufacturerSpecificCluster

from zhaquirks.const import (
DEVICE_TYPE,
Expand All @@ -26,47 +26,171 @@
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.danfoss import D5X84YU, DANFOSS

from . import DANFOSS, HIVE, POPP
Caius-Bonus marked this conversation as resolved.
Show resolved Hide resolved

MANUFACTURER = 0x1246

# 0x0201
danfoss_thermostat_attr = {
0x4000: ("open_window_detection", t.enum8, "rp"),
0x4003: ("external_open_window_detected", t.Bool, "rpw"),
0x4051: ("window_open_feature", t.Bool, "rpw"),
0x4010: ("exercise_day_of_week", t.enum8, "rpw"),
0x4011: ("exercise_trigger_time", t.uint16_t, "rpw"),
0x4012: ("mounting_mode_active", t.Bool, "rp"),
0x4013: ("mounting_mode_control", t.Bool, "rpw"), # undocumented
0x4014: ("orientation", t.enum8, "rpw"),
0x4015: ("external_measured_room_sensor", t.int16s, "rpw"),
0x4016: ("radiator_covered", t.Bool, "rpw"),
0x4030: ("heat_available", t.Bool, "rpw"), # undocumented
0x4031: ("heat_required", t.Bool, "rp"), # undocumented
0x4032: ("load_balancing_enable", t.Bool, "rpw"),
0x4040: ("load_room_mean", t.int16s, "rpw"),
0x404A: ("load_estimate", t.int16s, "rp"),
0x4020: ("control_algorithm_scale_factor", t.uint8_t, "rpw"),
0x404B: ("regulation_setpoint_offset", t.int8s, "rpw"),
0x404C: ("adaptation_run_control", t.enum8, "rw"),
0x404D: ("adaptation_run_status", t.bitmap8, "rp"),
0x404E: ("adaptation_run_settings", t.bitmap8, "rw"),
0x404F: ("preheat_status", t.Bool, "rp"),
0x4050: ("preheat_time", t.uint32_t, "rp"),
}
# ZCL Attributes Supported: pi_heating_demand (0x0008)

# reading mandatory ZCL attribute 0xFFFD results in UNSUPPORTED_ATTRIBUTE
# ZCL Commands Supported: SetWeeklySchedule (0x01), GetWeeklySchedule (0x02), ClearWeeklySchedule (0x03)

# Danfos says they support the following, but Popp eT093WRO responds with UNSUPPORTED_ATTRIBUTE
# 0x0003, 0x0004, 0x0015, 0x0016, 0x0025, 0x0030, 0x0020, 0x0021, 0x0022

# 0x0204
danfoss_interface_attr = {
0x4000: ("viewing_direction", t.enum8, "rpw"),
}

# Writing to mandatory ZCL attribute 0x0000 doesn't seem to do anything
# ZCL Attributes Supported: keypad_lockout (0x0001)

# 0x0b05
danfoss_diagnostic_attr = {
0x4000: ("sw_error_code", t.bitmap16, "rp"),
0x4001: ("wake_time_avg", t.uint32_t, "rp"), # always 0?
0x4002: ("wake_time_max_duration", t.uint32_t, "rp"), # always 0?
0x4003: ("wake_time_min_duration", t.uint32_t, "rp"), # always 0?
0x4004: ("sleep_postponed_count_avg", t.uint32_t, "rp"), # always 0?
0x4005: ("sleep_postponed_count_max", t.uint32_t, "rp"), # always 0?
0x4006: ("sleep_postponed_count_min", t.uint32_t, "rp"), # always 0?
0x4010: ("motor_step_counter", t.uint32_t, "rp"),
}


async def read_attributes(dest, source, dictionary):
"""Automatically reads attributes from source cluster and stores them in the dest cluster."""
response = {}
step = 14 # The device doesn't respond to more than 14 per request it seems

# read from source
for a in range(0, len(dictionary) + step, step):
subset = list(dictionary.keys())[a : a + step]
if subset:
response.update(
(await source.read_attributes(subset, manufacturer=MANUFACTURER))[0]
)

# store all of them in dest
for attrid, value in response.items():
dest.update_attribute(attrid, value)


class DanfossTRVCluster(CustomCluster, ManufacturerSpecificCluster):
"""Danfoss custom TRV cluster."""

cluster_id = 0xFC03
ep_attribute = "danfoss_trv_cluster"

attributes = ManufacturerSpecificCluster.attributes.copy()
attributes.update(danfoss_thermostat_attr)

async def write_attributes(self, attributes, manufacturer=None):
"""Write attributes to thermostat cluster."""
return await self.endpoint.thermostat.write_attributes(attributes, manufacturer)

async def bind(self):
"""Read attributes before ZHA binds, this makes sure the entity is created."""
await read_attributes(self, self.endpoint.thermostat, danfoss_thermostat_attr)
Caius-Bonus marked this conversation as resolved.
Show resolved Hide resolved

return await super().bind()


class DanfossTRVInterfaceCluster(CustomCluster, ManufacturerSpecificCluster):
"""Danfoss custom interface cluster."""

cluster_id = 0xFC04
ep_attribute = "danfoss_trv_interface_cluster"

attributes = ManufacturerSpecificCluster.attributes.copy()
attributes.update(danfoss_interface_attr)

async def write_attributes(self, attributes, manufacturer=None):
"""Write attributes to thermostat user interface cluster."""

return await self.endpoint.thermostat_ui.write_attributes(
attributes, manufacturer
)

async def bind(self):
"""Read attributes before ZHA binds, this makes sure the entity is created."""
await read_attributes(self, self.endpoint.thermostat_ui, danfoss_interface_attr)

return await super().bind()


class DanfossTRVDiagnosticCluster(CustomCluster, ManufacturerSpecificCluster):
"""Danfoss custom diagnostic cluster."""

cluster_id = 0xFC05
ep_attribute = "danfoss_trv_diagnostic_cluster"

attributes = ManufacturerSpecificCluster.attributes.copy()
attributes.update(danfoss_diagnostic_attr)

async def write_attributes(self, attributes, manufacturer=None):
"""Write attributes to diagnostic cluster."""
return await self.endpoint.diagnostic.write_attributes(attributes, manufacturer)

async def bind(self):
"""Read attributes before ZHA binds, this makes sure the entity is created."""
await read_attributes(self, self.endpoint.diagnostic, danfoss_diagnostic_attr)

return await super().bind()


class DanfossThermostatCluster(CustomCluster, Thermostat):
"""Danfoss custom cluster."""
"""Danfoss cluster for ZCL attributes and forwarding proprietary the attributes."""

server_commands = Thermostat.server_commands.copy()
server_commands[0x40] = foundation.ZCLCommandDef(
"setpoint_command",
{"param1": t.enum8, "param2": t.int16s},
is_manufacturer_specific=True,
)
server_commands = {
0x40: foundation.ZCLCommandDef(
"setpoint_command",
# Types
# 0: Schedule (relatively slow)
# 1: User Interaction (aggressive change)
# 2: Preheat (invisible to user)
{"type": t.enum8, "heating_setpoint": t.int16s},
is_manufacturer_specific=True,
),
# for synchronizing multiple TRVs preheating
0x42: foundation.ZCLCommandDef(
"preheat_command",
# Force: 0 means force, other values for future needs
{"force": t.enum8, "timestamp": t.uint32_t},
is_manufacturer_specific=True,
),
}

attributes = Thermostat.attributes.copy()
attributes.update(
{
0x4000: ("etrv_open_windows_detection", t.enum8, True),
0x4003: ("external_open_windows_detected", t.Bool, True),
0x4010: ("exercise_day_of_week", t.enum8, True),
0x4011: ("exercise_trigger_time", t.uint16_t, True),
0x4012: ("mounting_mode_active", t.Bool, True),
0x4013: ("mounting_mode_control", t.Bool, True),
0x4014: ("orientation", t.Bool, True),
0x4015: ("external_measured_room_sensor", t.int16s, True),
0x4016: ("radiator_covered", t.Bool, True),
0x4020: ("control_algorithm_scale_factor", t.uint8_t, True),
0x4030: ("heat_available", t.Bool, True),
0x4031: ("heat_supply_request", t.Bool, True),
0x4032: ("load_balancing_enable", t.Bool, True),
0x4040: ("load_radiator_room_mean", t.uint16_t, True),
0x404A: ("load_estimate_radiator", t.uint16_t, True),
0x404B: ("regulation_setPoint_offset", t.int8s, True),
0x404C: ("adaptation_run_control", t.enum8, True),
0x404D: ("adaptation_run_status", t.bitmap8, True),
0x404E: ("adaptation_run_settings", t.bitmap8, True),
0x404F: ("preheat_status", t.Bool, True),
0x4050: ("preheat_time", t.uint32_t, True),
0x4051: ("window_open_feature_on_off", t.Bool, True),
0xFFFD: ("cluster_revision", t.uint16_t, True),
}
)
attributes.update(danfoss_thermostat_attr)

async def write_attributes(self, attributes, manufacturer=None):
"""Send SETPOINT_COMMAND after setpoint change."""
Expand All @@ -82,52 +206,64 @@ async def write_attributes(self, attributes, manufacturer=None):
await self.setpoint_command(
0x01, attributes["occupied_heating_setpoint"], manufacturer=manufacturer
)
elif "system_mode" in attributes and attributes["system_mode"] == 0:
# Thermostatic Radiator Valves often cannot be turned off to prevent damage during frost
# just turn setpoint down to minimum of 5 degrees celsius
await self.setpoint_command(0x01, 500, manufacturer=manufacturer)

return write_res

def _update_attribute(self, attrid, value):
"""Update attributes of TRV cluster."""
if attrid in {a for (a, *_) in danfoss_thermostat_attr.values()}:
self.endpoint.danfoss_trv_cluster.update_attribute(attrid, value)

# update local either way
super()._update_attribute(attrid, value)


class DanfossUserInterfaceCluster(CustomCluster, UserInterface):
"""Danfoss custom cluster."""
"""Danfoss cluster for ZCL attributes and forwarding proprietary the attributes."""

attributes = UserInterface.attributes.copy()
attributes.update(
{
0x4000: ("viewing_direction", t.enum8, True),
}
)
attributes.update(danfoss_interface_attr)

def _update_attribute(self, attrid, value):
"""Update attributes of TRV interface cluster."""
if attrid in {a for (a, *_) in danfoss_interface_attr.values()}:
self.endpoint.danfoss_trv_interface_cluster.update_attribute(attrid, value)

# update local either way
super()._update_attribute(attrid, value)


class DanfossDiagnosticCluster(CustomCluster, Diagnostic):
"""Danfoss custom cluster."""
"""Danfoss cluster for ZCL attributes and forwarding proprietary the attributes."""

attributes = Diagnostic.attributes.copy()
attributes.update(
{
0x4000: ("sw_error_code", t.bitmap16, True),
0x4001: ("wake_time_avg", t.uint32_t, True),
0x4002: ("wake_time_max_duration", t.uint32_t, True),
0x4003: ("wake_time_min_duration", t.uint32_t, True),
0x4004: ("sleep_postponed_count_avg", t.uint32_t, True),
0x4005: ("sleep_postponed_count_max", t.uint32_t, True),
0x4006: ("sleep_postponed_count_min", t.uint32_t, True),
0x4010: ("motor_step_counter", t.uint32_t, True),
}
)
attributes.update(danfoss_diagnostic_attr)

def _update_attribute(self, attrid, value):
"""Update attributes or TRV diagnostic cluster."""
if attrid in {a for (a, *_) in danfoss_diagnostic_attr.values()}:
self.endpoint.danfoss_trv_diagnostic_cluster.update_attribute(attrid, value)

# update local either way
super()._update_attribute(attrid, value)


class DanfossThermostat(CustomDevice):
"""DanfossThermostat custom device."""

signature = {
# <SimpleDescriptor endpoint=1 profile=260 device_type=769
# device_version=0 input_clusters=[0, 1, 3, 10,32, 513, 516, 1026, 2821]
# output_clusters=[0, 25]>
MODELS_INFO: [
(DANFOSS, "TRV001"),
(DANFOSS, "eTRV0100"),
(DANFOSS, "eTRV0101"),
(DANFOSS, "eTRV0103"),
(D5X84YU, "eT093WRO"),
(POPP, "eT093WRO"),
(POPP, "eT093WRG"),
(HIVE, "TRV001"),
(HIVE, "TRV003"),
],
ENDPOINTS: {
1: {
Expand Down Expand Up @@ -160,6 +296,9 @@ class DanfossThermostat(CustomDevice):
DanfossThermostatCluster,
DanfossUserInterfaceCluster,
DanfossDiagnosticCluster,
DanfossTRVCluster,
DanfossTRVInterfaceCluster,
DanfossTRVDiagnosticCluster,
],
OUTPUT_CLUSTERS: [Basic, Ota],
}
Expand Down
2 changes: 0 additions & 2 deletions zhaquirks/tuya/ts0601_dimmer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
class TuyaInWallLevelControlNM(NoManufacturerCluster, TuyaInWallLevelControl):
"""Tuya Level cluster for inwall dimmable device with NoManufacturerID."""

pass


# --- DEVICE SUMMARY ---
# TuyaSingleSwitchDimmer: 0x00, 0x04, 0x05, 0xEF00; 0x000A, 0x0019
Expand Down
2 changes: 0 additions & 2 deletions zhaquirks/xbee/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,6 @@ def serialize(self):
class FrameId(uint8_t):
"""Frame ID type."""

pass


class NWK(int):
"""Network address serializable class."""
Expand Down