Skip to content

Commit ee6dfc0

Browse files
authored
Merge pull request #804 from BenPru/chore/upstream-datatypes-mk3-celsius
chore(lux_overrides): 🔧 adopt upstream datatypes for the Mk3 timer block, limit temperatures and counters
2 parents 4f93bf9 + d3473fc commit ee6dfc0

2 files changed

Lines changed: 144 additions & 5 deletions

File tree

‎custom_components/luxtronik2/lux_overrides.py‎

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
Percent,
1515
Percent2,
1616
Power,
17+
Seconds,
1718
SelectionBase,
1819
SwitchoffFile,
1920
Timestamp,
@@ -450,22 +451,45 @@ def update_Luxtronik_Parameters():
450451
Parameters.parameters.update(parameters_to_add_update) # pyright: ignore[reportCallIssue, reportArgumentType]
451452
Calculations.calculations.update(calculations_to_add_update) # pyright: ignore[reportCallIssue, reportArgumentType]
452453

453-
# example bulk update of parameter classes for a range of numbers
454-
Celsius_numbers = [14, 15, 16, 141, 142, 143, 774, 775, 776] + [17, 47, 90, 93, 111]
454+
# Temperature settings stored as tenths of a degree. The last six are
455+
# limit temperatures upstream `main` types as Celsius too; all 30 units
456+
# in the diagnostics corpus hold plausible tenths there: 84 at 650-700,
457+
# 87 at 350-650, 91 at 350-450, 92 at -200/-220, 94 at 1150-1400 and
458+
# 96 at 500 on every unit.
459+
Celsius_numbers = (
460+
[14, 15, 16, 141, 142, 143, 774, 775, 776]
461+
+ [17, 47, 90, 93, 111]
462+
+ [84, 87, 91, 92, 94, 96]
463+
)
455464
update_Luxtronik_Parameter_Classes(Celsius_numbers, Celsius)
456465

457466
# Kelvin temperature-difference parameters stored as tenths.
458467
delta_temperature_numbers = [88, 89]
459468
update_Luxtronik_Parameter_Classes(delta_temperature_numbers, Kelvin)
460469

470+
# Operating-time counters (ID_Zaehler_BetrZeit*) in seconds, the
471+
# parameter-side mirrors of calculations 56-66, and the heat-quantity
472+
# date (ID_Waermemenge_Datum) as a Unix timestamp - eight corpus units
473+
# sit on its 2018-01-01 factory default, eight more on 0, which the
474+
# library's Timestamp renders as 1970-01-01 (as it does for 6/7/731/732).
475+
# Typed as upstream `main` does; nothing reads them, this only makes
476+
# diagnostics dumps readable.
477+
operating_time_numbers = [668, 669, 670, 671, 672, 673, 728, 729, 730, 859]
478+
update_Luxtronik_Parameter_Classes(operating_time_numbers, Seconds)
479+
update_Luxtronik_Parameter_Classes([880], Timestamp)
480+
461481
# Timer program schedule parameters: mostly TimeOfDay entries, with a
462482
# handful of TimerProgram mode selectors interspersed. 162-667 holds the
463-
# heating, mixing, DHW, circulation-pump and pool circuits.
483+
# heating, mixing circuits 1/2, DHW, circulation-pump and pool circuits;
484+
# mixing circuit 3 sits apart at 788 (selector) and 789-848 (times).
464485
# 607 is named ID_Einst_SuSwb_akt upstream but holds a time of day on
465486
# every unit seen with a non-zero value there (06:30/07:00/07:30, each
466487
# followed by an end time in 608 - #789), so it stays in the time range.
467-
timer_program_numbers = {222, 283, 344, 405, 506}
468-
time_of_day_numbers = [n for n in range(162, 668) if n not in timer_program_numbers]
488+
timer_program_numbers = {222, 283, 344, 405, 506, 788}
489+
schedule_numbers = list(range(162, 668)) + list(range(788, 849))
490+
time_of_day_numbers = [
491+
n for n in schedule_numbers if n not in timer_program_numbers
492+
]
469493
update_Luxtronik_Parameter_Classes(time_of_day_numbers, TimeOfDay)
470494
update_Luxtronik_Parameter_Classes(list(timer_program_numbers), TimerProgram)
471495
# The ventilation circuit sits apart at 895 (selector) and 896-955, and

‎tests/test_lux_overrides.py‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,121 @@ def test_heating_block_is_still_covered(self):
843843
for number in (223, 282):
844844
assert isinstance(parameters[number], TimeOfDay), number
845845

846+
def test_mixing_circuit_3_block_is_covered(self):
847+
"""788-848 is the Mk3 circuit, apart from the 162-667 run.
848+
849+
Same selector + WO/25/TG shape as the others (upstream `main` types
850+
it that way); two corpus units carry 34200 / 18000 (09:30 / 05:00)
851+
in 789, the rest hold 0.
852+
"""
853+
from custom_components.luxtronik2.lux_overrides import (
854+
TimeOfDay,
855+
TimerProgram,
856+
)
857+
858+
parameters = self._applied()
859+
selector = parameters[788]
860+
assert selector.name == "ID_Einst_SuMk3_akt2"
861+
assert isinstance(selector, TimerProgram)
862+
assert selector.from_heatpump(0) == "week"
863+
for number in (789, 848):
864+
assert isinstance(parameters[number], TimeOfDay), number
865+
assert parameters[789].name == "ID_Einst_SuMk3Wo_zeit_0_0"
866+
assert parameters[789].from_heatpump(34200) == "09:30"
867+
assert parameters[848].name == "ID_Einst_SuMk3Tg_zeit_2_13"
868+
# The block is 788-848: 787 (ID_SU_FstdMK3) and 849
869+
# (ID_Ba_Hz_MK3_saved) are not schedule registers.
870+
assert not isinstance(parameters[787], TimeOfDay | TimerProgram)
871+
assert not isinstance(parameters[849], TimeOfDay | TimerProgram)
872+
873+
874+
class TestUpstreamCelsiusParameters:
875+
"""Six limit temperatures upstream `main` types as Celsius (tenths).
876+
877+
All 30 corpus units store plausible tenths (84 at 650-700, 87 at
878+
350-650, 91 at 350-450, 92 at -200/-220, 94 at 1150-1400, 96 at 500
879+
everywhere), which also matches the setting each one names.
880+
"""
881+
882+
_EXPECTED = {
883+
84: ("ID_Sollwert_TLG_max", 700, 70.0),
884+
87: ("ID_Einst_TRBegr_akt", 560, 56.0),
885+
91: ("ID_Einst_TAmax_akt", 350, 35.0),
886+
92: ("ID_Einst_TAmin_akt", -200, -20.0),
887+
94: ("ID_Einst_THGmax_akt", 1150, 115.0),
888+
96: ("ID_Einst_TV2VDBW_akt", 500, 50.0),
889+
}
890+
891+
def test_are_celsius_in_tenths(self):
892+
from luxtronik.datatypes import Celsius
893+
from luxtronik.parameters import Parameters
894+
895+
from custom_components.luxtronik2.lux_overrides import (
896+
update_Luxtronik_Parameters,
897+
)
898+
899+
update_Luxtronik_Parameters()
900+
for number, (name, raw, value) in self._EXPECTED.items():
901+
parameter = Parameters.parameters[number]
902+
assert parameter.name == name, number
903+
assert isinstance(parameter, Celsius), number
904+
assert parameter.from_heatpump(raw) == value, number
905+
906+
907+
class TestUpstreamCounterParameters:
908+
"""Operating-time counters and the heat-quantity date, typed as upstream
909+
`main` does. Nothing in the integration reads these registers (the
910+
sensors use the calculation mirrors 56-66); this only makes diagnostics
911+
dumps readable.
912+
"""
913+
914+
_SECONDS = {
915+
668: "ID_Zaehler_BetrZeitWP",
916+
669: "ID_Zaehler_BetrZeitVD1",
917+
670: "ID_Zaehler_BetrZeitVD2",
918+
671: "ID_Zaehler_BetrZeitZWE1",
919+
672: "ID_Zaehler_BetrZeitZWE2",
920+
673: "ID_Zaehler_BetrZeitZWE3",
921+
728: "ID_Zaehler_BetrZeitHz",
922+
729: "ID_Zaehler_BetrZeitBW",
923+
730: "ID_Zaehler_BetrZeitKue",
924+
859: "ID_Zaehler_BetrZeitSW",
925+
}
926+
927+
def _applied(self):
928+
from luxtronik.parameters import Parameters
929+
930+
from custom_components.luxtronik2.lux_overrides import (
931+
update_Luxtronik_Parameters,
932+
)
933+
934+
update_Luxtronik_Parameters()
935+
return Parameters.parameters
936+
937+
def test_operating_time_counters_are_seconds(self):
938+
from luxtronik.datatypes import Seconds
939+
940+
parameters = self._applied()
941+
for number, name in self._SECONDS.items():
942+
assert parameters[number].name == name, number
943+
assert isinstance(parameters[number], Seconds), number
944+
# 60515833 s on one corpus unit, i.e. about 700 days of runtime.
945+
assert parameters[668].from_heatpump(60515833) == 60515833
946+
947+
def test_heat_quantity_date_is_a_timestamp(self):
948+
from datetime import date, datetime
949+
950+
from luxtronik.datatypes import Timestamp
951+
952+
parameter = self._applied()[880]
953+
assert parameter.name == "ID_Waermemenge_Datum"
954+
assert isinstance(parameter, Timestamp)
955+
# Eight corpus units sit on the 2018-01-01 factory default
956+
# (01:00 UTC; the library renders it in the host's local zone).
957+
rendered = parameter.from_heatpump(1514768400)
958+
assert isinstance(rendered, datetime)
959+
assert rendered.date() in (date(2017, 12, 31), date(2018, 1, 1))
960+
846961

847962
class TestSmartGridMode:
848963
"""P1030 is a four-option mode selector, so it needs a real datatype.

0 commit comments

Comments
 (0)