ramses-rf 0.51.4__py3-none-any.whl → 0.51.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ramses_cli/client.py +0 -1
- ramses_rf/const.py +2 -0
- ramses_rf/database.py +39 -24
- ramses_rf/device/base.py +2 -2
- ramses_rf/device/hvac.py +19 -4
- ramses_rf/dispatcher.py +8 -8
- ramses_rf/entity_base.py +29 -38
- ramses_rf/gateway.py +8 -16
- ramses_rf/system/heat.py +3 -3
- ramses_rf/system/zones.py +7 -5
- ramses_rf/version.py +1 -1
- {ramses_rf-0.51.4.dist-info → ramses_rf-0.51.6.dist-info}/METADATA +2 -2
- ramses_rf-0.51.6.dist-info/RECORD +55 -0
- ramses_tx/__init__.py +1 -1
- ramses_tx/address.py +1 -1
- ramses_tx/command.py +111 -3
- ramses_tx/const.py +3 -1
- ramses_tx/gateway.py +1 -1
- ramses_tx/helpers.py +64 -25
- ramses_tx/logger.py +26 -23
- ramses_tx/message.py +1 -1
- ramses_tx/parsers.py +25 -16
- ramses_tx/ramses.py +3 -1
- ramses_tx/transport.py +15 -11
- ramses_tx/typed_dicts.py +3 -2
- ramses_tx/version.py +1 -1
- ramses_rf-0.51.4.dist-info/RECORD +0 -55
- {ramses_rf-0.51.4.dist-info → ramses_rf-0.51.6.dist-info}/WHEEL +0 -0
- {ramses_rf-0.51.4.dist-info → ramses_rf-0.51.6.dist-info}/entry_points.txt +0 -0
- {ramses_rf-0.51.4.dist-info → ramses_rf-0.51.6.dist-info}/licenses/LICENSE +0 -0
ramses_tx/address.py
CHANGED
|
@@ -45,7 +45,7 @@ class Address:
|
|
|
45
45
|
# device_id = NON_DEVICE_ID
|
|
46
46
|
|
|
47
47
|
self.id = device_id # TODO: check is a valid id...
|
|
48
|
-
self.type = device_id[:2] # dex,
|
|
48
|
+
self.type = device_id[:2] # dex, drops 2nd part, incl. ":"
|
|
49
49
|
self._hex_id: str = None # type: ignore[assignment]
|
|
50
50
|
|
|
51
51
|
if not self.is_valid(device_id):
|
ramses_tx/command.py
CHANGED
|
@@ -39,6 +39,10 @@ from .const import (
|
|
|
39
39
|
)
|
|
40
40
|
from .frame import Frame, pkt_header
|
|
41
41
|
from .helpers import (
|
|
42
|
+
air_quality_code,
|
|
43
|
+
capability_bits,
|
|
44
|
+
fan_info_flags,
|
|
45
|
+
fan_info_to_byte,
|
|
42
46
|
hex_from_bool,
|
|
43
47
|
hex_from_double,
|
|
44
48
|
hex_from_dtm,
|
|
@@ -854,7 +858,7 @@ class Command(Frame):
|
|
|
854
858
|
def put_indoor_humidity(
|
|
855
859
|
cls, dev_id: DeviceIdT | str, indoor_humidity: float | None
|
|
856
860
|
) -> Command:
|
|
857
|
-
"""Constructor to announce the current humidity of a sensor (12A0)."""
|
|
861
|
+
"""Constructor to announce the current humidity of a sensor or fan (12A0)."""
|
|
858
862
|
# .I --- 37:039266 --:------ 37:039266 1298 003 000316
|
|
859
863
|
|
|
860
864
|
payload = "00" + hex_from_percent(indoor_humidity, high_res=False)
|
|
@@ -1320,6 +1324,109 @@ class Command(Frame):
|
|
|
1320
1324
|
dt_str = hex_from_dtm(datetime, is_dst=is_dst, incl_seconds=True)
|
|
1321
1325
|
return cls.from_attrs(W_, ctl_id, Code._313F, f"0060{dt_str}")
|
|
1322
1326
|
|
|
1327
|
+
@classmethod # constructor for I|31DA
|
|
1328
|
+
def get_hvac_fan_31da(
|
|
1329
|
+
cls,
|
|
1330
|
+
dev_id: DeviceIdT | str,
|
|
1331
|
+
hvac_id: str,
|
|
1332
|
+
bypass_position: float | None,
|
|
1333
|
+
air_quality: int | None,
|
|
1334
|
+
co2_level: int | None,
|
|
1335
|
+
indoor_humidity: float | None,
|
|
1336
|
+
outdoor_humidity: float | None,
|
|
1337
|
+
exhaust_temp: float | None,
|
|
1338
|
+
supply_temp: float | None,
|
|
1339
|
+
indoor_temp: float | None,
|
|
1340
|
+
outdoor_temp: float | None,
|
|
1341
|
+
speed_capabilities: list[str],
|
|
1342
|
+
fan_info: str,
|
|
1343
|
+
_unknown_fan_info_flags: list[int], # skip? as starts with _
|
|
1344
|
+
exhaust_fan_speed: float | None,
|
|
1345
|
+
supply_fan_speed: float | None,
|
|
1346
|
+
remaining_mins: int | None,
|
|
1347
|
+
post_heat: int | None,
|
|
1348
|
+
pre_heat: int | None,
|
|
1349
|
+
supply_flow: float | None,
|
|
1350
|
+
exhaust_flow: float | None,
|
|
1351
|
+
**kwargs: Any, # option: air_quality_basis: str | None,
|
|
1352
|
+
) -> Command:
|
|
1353
|
+
"""Constructor to announce hvac fan (state, temps, flows, humidity etc.) of a HRU (31DA)."""
|
|
1354
|
+
# 00 EF00 7FFF 34 33 0898 0898 088A 0882 F800 00 15 14 14 0000 EF EF 05F5 0613:
|
|
1355
|
+
# {"hvac_id": '00', 'bypass_position': 0.000, 'air_quality': None,
|
|
1356
|
+
# 'co2_level': None, 'indoor_humidity': 0.52, 'outdoor_humidity': 0.51,
|
|
1357
|
+
# 'exhaust_temp': 22.0, 'supply_temp': 22.0, 'indoor_temp': 21.86,
|
|
1358
|
+
# 'outdoor_temp': 21.78, 'speed_capabilities': ['off', 'low_med_high',
|
|
1359
|
+
# 'timer', 'boost', 'auto'], 'fan_info': 'away',
|
|
1360
|
+
# '_unknown_fan_info_flags': [0, 0, 0], 'exhaust_fan_speed': 0.1,
|
|
1361
|
+
# 'supply_fan_speed': 0.1, 'remaining_mins': 0, 'post_heat': None,
|
|
1362
|
+
# 'pre_heat': None, 'supply_flow': 15.25, 'exhaust_flow': 15.55},
|
|
1363
|
+
|
|
1364
|
+
air_quality_basis: str = kwargs.pop("air_quality_basis", "00")
|
|
1365
|
+
extra: str = kwargs.pop("_extra", "")
|
|
1366
|
+
assert not kwargs, kwargs
|
|
1367
|
+
|
|
1368
|
+
payload = hvac_id
|
|
1369
|
+
payload += (
|
|
1370
|
+
f"{(int(air_quality * 200)):02X}" if air_quality is not None else "EF"
|
|
1371
|
+
)
|
|
1372
|
+
payload += (
|
|
1373
|
+
f"{air_quality_code(air_quality_basis)}"
|
|
1374
|
+
if air_quality_basis is not None
|
|
1375
|
+
else "00"
|
|
1376
|
+
)
|
|
1377
|
+
payload += f"{co2_level:04X}" if co2_level is not None else "7FFF"
|
|
1378
|
+
payload += (
|
|
1379
|
+
hex_from_percent(indoor_humidity, high_res=False)
|
|
1380
|
+
if indoor_humidity is not None
|
|
1381
|
+
else "EF"
|
|
1382
|
+
)
|
|
1383
|
+
payload += (
|
|
1384
|
+
hex_from_percent(outdoor_humidity, high_res=False)
|
|
1385
|
+
if outdoor_humidity is not None
|
|
1386
|
+
else "EF"
|
|
1387
|
+
)
|
|
1388
|
+
payload += hex_from_temp(exhaust_temp) if exhaust_temp is not None else "7FFF"
|
|
1389
|
+
payload += hex_from_temp(supply_temp) if supply_temp is not None else "7FFF"
|
|
1390
|
+
payload += hex_from_temp(indoor_temp) if indoor_temp is not None else "7FFF"
|
|
1391
|
+
payload += hex_from_temp(outdoor_temp) if outdoor_temp is not None else "7FFF"
|
|
1392
|
+
payload += (
|
|
1393
|
+
f"{capability_bits(speed_capabilities):04X}"
|
|
1394
|
+
if speed_capabilities is not None
|
|
1395
|
+
else "7FFF"
|
|
1396
|
+
)
|
|
1397
|
+
payload += (
|
|
1398
|
+
hex_from_percent(bypass_position, high_res=True)
|
|
1399
|
+
if bypass_position is not None
|
|
1400
|
+
else "EF"
|
|
1401
|
+
)
|
|
1402
|
+
payload += (
|
|
1403
|
+
f"{(fan_info_to_byte(fan_info) | fan_info_flags(_unknown_fan_info_flags)):02X}"
|
|
1404
|
+
if fan_info is not None
|
|
1405
|
+
else "EF"
|
|
1406
|
+
)
|
|
1407
|
+
payload += (
|
|
1408
|
+
hex_from_percent(exhaust_fan_speed, high_res=True)
|
|
1409
|
+
if exhaust_fan_speed is not None
|
|
1410
|
+
else "FF"
|
|
1411
|
+
)
|
|
1412
|
+
payload += (
|
|
1413
|
+
hex_from_percent(supply_fan_speed, high_res=True)
|
|
1414
|
+
if supply_fan_speed is not None
|
|
1415
|
+
else "FF"
|
|
1416
|
+
)
|
|
1417
|
+
payload += f"{remaining_mins:04X}" if remaining_mins is not None else "7FFF"
|
|
1418
|
+
payload += f"{int(post_heat * 200):02X}" if post_heat is not None else "EF"
|
|
1419
|
+
payload += f"{int(pre_heat * 200):02X}" if pre_heat is not None else "EF"
|
|
1420
|
+
payload += (
|
|
1421
|
+
f"{(int(supply_flow * 100)):04X}" if supply_flow is not None else "7FFF"
|
|
1422
|
+
)
|
|
1423
|
+
payload += (
|
|
1424
|
+
f"{(int(exhaust_flow * 100)):04X}" if exhaust_flow is not None else "7FFF"
|
|
1425
|
+
)
|
|
1426
|
+
payload += extra
|
|
1427
|
+
|
|
1428
|
+
return cls._from_attrs(I_, Code._31DA, payload, addr0=dev_id, addr2=dev_id)
|
|
1429
|
+
|
|
1323
1430
|
@classmethod # constructor for RQ|3220
|
|
1324
1431
|
def get_opentherm_data(cls, otb_id: DeviceIdT | str, msg_id: int | str) -> Command:
|
|
1325
1432
|
"""Constructor to get (Read-Data) opentherm msg value (c.f. parser_3220)."""
|
|
@@ -1412,7 +1519,7 @@ CODE_API_MAP = {
|
|
|
1412
1519
|
f"{I_}|{Code._1FC9}": Command.put_bind,
|
|
1413
1520
|
f"{W_}|{Code._1FC9}": Command.put_bind, # NOTE: same class method as I|1FC9
|
|
1414
1521
|
f"{W_}|{Code._22F7}": Command.set_bypass_position,
|
|
1415
|
-
f"{I_}|{Code._1298}": Command.put_co2_level,
|
|
1522
|
+
f"{I_}|{Code._1298}": Command.put_co2_level, # . has a test
|
|
1416
1523
|
f"{RQ}|{Code._1F41}": Command.get_dhw_mode,
|
|
1417
1524
|
f"{W_}|{Code._1F41}": Command.set_dhw_mode, # . has a test
|
|
1418
1525
|
f"{RQ}|{Code._10A0}": Command.get_dhw_params,
|
|
@@ -1421,7 +1528,7 @@ CODE_API_MAP = {
|
|
|
1421
1528
|
f"{I_}|{Code._1260}": Command.put_dhw_temp, # . has a test (empty)
|
|
1422
1529
|
f"{I_}|{Code._22F1}": Command.set_fan_mode,
|
|
1423
1530
|
f"{W_}|{Code._2411}": Command.set_fan_param,
|
|
1424
|
-
f"{I_}|{Code._12A0}": Command.put_indoor_humidity,
|
|
1531
|
+
f"{I_}|{Code._12A0}": Command.put_indoor_humidity, # . has a test
|
|
1425
1532
|
f"{RQ}|{Code._1030}": Command.get_mix_valve_params,
|
|
1426
1533
|
f"{W_}|{Code._1030}": Command.set_mix_valve_params, # . has a test
|
|
1427
1534
|
f"{RQ}|{Code._3220}": Command.get_opentherm_data,
|
|
@@ -1451,4 +1558,5 @@ CODE_API_MAP = {
|
|
|
1451
1558
|
f"{W_}|{Code._2309}": Command.set_zone_setpoint, # . has a test
|
|
1452
1559
|
f"{RQ}|{Code._30C9}": Command.get_zone_temp,
|
|
1453
1560
|
f"{RQ}|{Code._12B0}": Command.get_zone_window_state,
|
|
1561
|
+
f"{I_}|{Code._31DA}": Command.get_hvac_fan_31da, # . has a test
|
|
1454
1562
|
} # TODO: RQ|0404 (Zone & DHW)
|
ramses_tx/const.py
CHANGED
|
@@ -88,6 +88,8 @@ SZ_REL_HUMIDITY: Final = "rel_humidity"
|
|
|
88
88
|
SZ_REMAINING_DAYS: Final = "days_remaining"
|
|
89
89
|
SZ_REMAINING_MINS: Final = "remaining_mins"
|
|
90
90
|
SZ_REMAINING_PERCENT: Final = "percent_remaining"
|
|
91
|
+
SZ_REQ_REASON: Final = "req_reason"
|
|
92
|
+
SZ_REQ_SPEED: Final = "req_speed"
|
|
91
93
|
SZ_SUPPLY_FAN_SPEED: Final = "supply_fan_speed"
|
|
92
94
|
SZ_SUPPLY_FLOW: Final = "supply_flow"
|
|
93
95
|
SZ_SUPPLY_TEMP: Final = "supply_temp"
|
|
@@ -440,7 +442,7 @@ DEV_TYPE_MAP = attr_dict_factory(
|
|
|
440
442
|
"HEAT_ZONE_ACTUATORS": ("00", "02", "04", "13"),
|
|
441
443
|
"THM_DEVICES": ("03", "12", "22", "34"),
|
|
442
444
|
"TRV_DEVICES": ("00", "04"),
|
|
443
|
-
"CONTROLLERS": ("01", "12", "22", "23", "34"), # potentially controllers
|
|
445
|
+
"CONTROLLERS": ("01", "02", "12", "22", "23", "34"), # potentially controllers
|
|
444
446
|
"PROMOTABLE_SLUGS": (DevType.DEV, DevType.HEA, DevType.HVC),
|
|
445
447
|
"HVAC_SLUGS": {
|
|
446
448
|
DevType.CO2: "co2_sensor",
|
ramses_tx/gateway.py
CHANGED
|
@@ -331,7 +331,7 @@ class Engine:
|
|
|
331
331
|
) # may: raise ProtocolError/ProtocolSendFailed
|
|
332
332
|
|
|
333
333
|
def _msg_handler(self, msg: Message) -> None:
|
|
334
|
-
# HACK: This is one consequence of an
|
|
334
|
+
# HACK: This is one consequence of an unpleasant anachronism
|
|
335
335
|
msg.__class__ = Message # HACK (next line too)
|
|
336
336
|
msg._gwy = self # type: ignore[assignment]
|
|
337
337
|
|
ramses_tx/helpers.py
CHANGED
|
@@ -479,6 +479,13 @@ def parse_valve_demand(
|
|
|
479
479
|
return {SZ_HEAT_DEMAND: result}
|
|
480
480
|
|
|
481
481
|
|
|
482
|
+
AIR_QUALITY_BASIS: dict[str, str] = {
|
|
483
|
+
"10": "voc", # volatile compounds
|
|
484
|
+
"20": "co2", # carbon dioxide
|
|
485
|
+
"40": "rel_humidity", # relative humidity
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
|
|
482
489
|
# 31DA[2:6] and 12C8[2:6]
|
|
483
490
|
def parse_air_quality(value: HexStr4) -> PayDictT.AIR_QUALITY:
|
|
484
491
|
"""Return the air quality (%): poor (0.0) to excellent (1.0).
|
|
@@ -505,15 +512,21 @@ def parse_air_quality(value: HexStr4) -> PayDictT.AIR_QUALITY:
|
|
|
505
512
|
assert level <= 1.0, value[:2] # TODO: raise exception
|
|
506
513
|
|
|
507
514
|
assert value[2:] in ("10", "20", "40"), value[2:] # TODO: remove assert
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}.get(value[2:], f"unknown_{value[2:]}") # TODO: remove get/unknown
|
|
515
|
+
|
|
516
|
+
basis: str = AIR_QUALITY_BASIS.get(
|
|
517
|
+
value[2:], f"unknown_{value[2:]}"
|
|
518
|
+
) # TODO: remove get/unknown
|
|
513
519
|
|
|
514
520
|
return {SZ_AIR_QUALITY: level, SZ_AIR_QUALITY_BASIS: basis}
|
|
515
521
|
|
|
516
522
|
|
|
523
|
+
def air_quality_code(desc: str) -> str:
|
|
524
|
+
for k, v in AIR_QUALITY_BASIS.items():
|
|
525
|
+
if v == desc:
|
|
526
|
+
return k
|
|
527
|
+
return "00"
|
|
528
|
+
|
|
529
|
+
|
|
517
530
|
# 31DA[6:10] and 1298[2:6]
|
|
518
531
|
def parse_co2_level(value: HexStr4) -> PayDictT.CO2_LEVEL:
|
|
519
532
|
"""Return the co2 level (ppm).
|
|
@@ -657,6 +670,26 @@ def _parse_hvac_temp(param_name: str, value: HexStr4) -> Mapping[str, float | No
|
|
|
657
670
|
return {param_name: temp}
|
|
658
671
|
|
|
659
672
|
|
|
673
|
+
ABILITIES = {
|
|
674
|
+
15: "off",
|
|
675
|
+
14: "low_med_high", # 3,2,1 = high,med,low?
|
|
676
|
+
13: "timer",
|
|
677
|
+
12: "boost",
|
|
678
|
+
11: "auto",
|
|
679
|
+
10: "speed_4",
|
|
680
|
+
9: "speed_5",
|
|
681
|
+
8: "speed_6",
|
|
682
|
+
7: "speed_7",
|
|
683
|
+
6: "speed_8",
|
|
684
|
+
5: "speed_9",
|
|
685
|
+
4: "speed_10",
|
|
686
|
+
3: "auto_night",
|
|
687
|
+
2: "reserved",
|
|
688
|
+
1: "post_heater",
|
|
689
|
+
0: "pre_heater",
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
|
|
660
693
|
# 31DA[30:34]
|
|
661
694
|
def parse_capabilities(value: HexStr4) -> PayDictT.CAPABILITIES:
|
|
662
695
|
"""Return the speed capabilities (a bitmask).
|
|
@@ -672,25 +705,6 @@ def parse_capabilities(value: HexStr4) -> PayDictT.CAPABILITIES:
|
|
|
672
705
|
if value == "7FFF": # TODO: Not implemented???
|
|
673
706
|
return {SZ_SPEED_CAPABILITIES: None}
|
|
674
707
|
|
|
675
|
-
ABILITIES = {
|
|
676
|
-
15: "off",
|
|
677
|
-
14: "low_med_high", # 3,2,1 = high,med,low?
|
|
678
|
-
13: "timer",
|
|
679
|
-
12: "boost",
|
|
680
|
-
11: "auto",
|
|
681
|
-
10: "speed_4",
|
|
682
|
-
9: "speed_5",
|
|
683
|
-
8: "speed_6",
|
|
684
|
-
7: "speed_7",
|
|
685
|
-
6: "speed_8",
|
|
686
|
-
5: "speed_9",
|
|
687
|
-
4: "speed_10",
|
|
688
|
-
3: "auto_night",
|
|
689
|
-
2: "reserved",
|
|
690
|
-
1: "post_heater",
|
|
691
|
-
0: "pre_heater",
|
|
692
|
-
}
|
|
693
|
-
|
|
694
708
|
# assert value in ("0002", "4000", "4808", "F000", "F001", "F800", "F808"), value
|
|
695
709
|
|
|
696
710
|
return {
|
|
@@ -700,6 +714,16 @@ def parse_capabilities(value: HexStr4) -> PayDictT.CAPABILITIES:
|
|
|
700
714
|
}
|
|
701
715
|
|
|
702
716
|
|
|
717
|
+
def capability_bits(cap_list: list[str]) -> int:
|
|
718
|
+
# 0xF800 = 0b1111100000000000
|
|
719
|
+
cap_res: int = 0
|
|
720
|
+
for cap in cap_list:
|
|
721
|
+
for k, v in ABILITIES.items():
|
|
722
|
+
if v == cap:
|
|
723
|
+
cap_res |= 2**k # set bit
|
|
724
|
+
return cap_res
|
|
725
|
+
|
|
726
|
+
|
|
703
727
|
# 31DA[34:36]
|
|
704
728
|
def parse_bypass_position(value: HexStr2) -> PayDictT.BYPASS_POSITION:
|
|
705
729
|
"""Return the bypass position (%), usually fully open or closed (0%, no bypass).
|
|
@@ -757,7 +781,22 @@ def parse_fan_info(value: HexStr2) -> PayDictT.FAN_INFO:
|
|
|
757
781
|
}
|
|
758
782
|
|
|
759
783
|
|
|
760
|
-
|
|
784
|
+
def fan_info_to_byte(info: str) -> int:
|
|
785
|
+
for k, v in _31DA_FAN_INFO.items():
|
|
786
|
+
if v == info:
|
|
787
|
+
return int(k) & 0x1F
|
|
788
|
+
return 0x0000
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def fan_info_flags(flags_list: list[int]) -> int:
|
|
792
|
+
flag_res: int = 0
|
|
793
|
+
for index, shft in enumerate(range(7, 4, -1)): # index = 7, 6 and 5
|
|
794
|
+
if flags_list[index] == 1:
|
|
795
|
+
flag_res |= 1 << shft # set bits
|
|
796
|
+
return flag_res
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
# 31DA[38:40], also 2210
|
|
761
800
|
def parse_exhaust_fan_speed(value: HexStr2) -> PayDictT.EXHAUST_FAN_SPEED:
|
|
762
801
|
"""Return the exhaust fan speed (% of max speed)."""
|
|
763
802
|
return _parse_fan_speed(SZ_EXHAUST_FAN_SPEED, value) # type: ignore[return-value]
|
ramses_tx/logger.py
CHANGED
|
@@ -7,7 +7,6 @@ This module wraps logger to provide bespoke functionality, especially for timest
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
|
-
import os
|
|
11
10
|
import re
|
|
12
11
|
import shutil
|
|
13
12
|
import sys
|
|
@@ -174,28 +173,32 @@ class TimedRotatingFileHandler(_TimedRotatingFileHandler):
|
|
|
174
173
|
# self.doRollover()
|
|
175
174
|
# return super().emit(record)
|
|
176
175
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
176
|
+
# To fix issue ramses_cc 293, test if this override is still required
|
|
177
|
+
# async def getFilesToDelete(self) -> list[str]: # zxdavb: my version
|
|
178
|
+
# """Determine the files to delete when rolling over.
|
|
179
|
+
#
|
|
180
|
+
# Overridden as old log files were not being deleted.
|
|
181
|
+
# """
|
|
182
|
+
# # See bpo-44753 (this code is as was before that commit), bpo45628, bpo-46063
|
|
183
|
+
# dirName, baseName = os.path.split(self.baseFilename)
|
|
184
|
+
# loop = asyncio.get_running_loop()
|
|
185
|
+
# # Must run async in executor to prevent HA blocking call on rollover (ramses_cc issue 293)
|
|
186
|
+
# file_names = await loop.run_in_executor(None, os.listdir, dirName) < doesn't work
|
|
187
|
+
#
|
|
188
|
+
# result = []
|
|
189
|
+
# prefix = baseName + "."
|
|
190
|
+
# plen = len(prefix)
|
|
191
|
+
# for fileName in file_names:
|
|
192
|
+
# if fileName[:plen] == prefix:
|
|
193
|
+
# suffix = fileName[plen:]
|
|
194
|
+
# if self.extMatch.match(suffix):
|
|
195
|
+
# result.append(os.path.join(dirName, fileName))
|
|
196
|
+
# if len(result) < self.backupCount:
|
|
197
|
+
# result = []
|
|
198
|
+
# else:
|
|
199
|
+
# result.sort()
|
|
200
|
+
# result = result[: len(result) - self.backupCount]
|
|
201
|
+
# return result
|
|
199
202
|
|
|
200
203
|
|
|
201
204
|
def getLogger( # permits a bespoke Logger class
|
ramses_tx/message.py
CHANGED
|
@@ -281,7 +281,7 @@ class MessageBase:
|
|
|
281
281
|
raise exc.PacketInvalid from err
|
|
282
282
|
|
|
283
283
|
|
|
284
|
-
class Message(MessageBase):
|
|
284
|
+
class Message(MessageBase):
|
|
285
285
|
"""Extend the Message class, so is useful to a stateful Gateway.
|
|
286
286
|
|
|
287
287
|
Adds _expired attr to the Message class.
|
ramses_tx/parsers.py
CHANGED
|
@@ -72,6 +72,7 @@ from .const import (
|
|
|
72
72
|
SZ_RELAY_DEMAND,
|
|
73
73
|
SZ_REMAINING_DAYS,
|
|
74
74
|
SZ_REMAINING_PERCENT,
|
|
75
|
+
SZ_REQ_REASON,
|
|
75
76
|
SZ_SETPOINT,
|
|
76
77
|
SZ_SETPOINT_BOUNDS,
|
|
77
78
|
SZ_SYSTEM_MODE,
|
|
@@ -1378,22 +1379,18 @@ def parser_1fd4(payload: str, msg: Message) -> PayDictT._1FD4:
|
|
|
1378
1379
|
return {"ticker": int(payload[2:], 16)}
|
|
1379
1380
|
|
|
1380
1381
|
|
|
1381
|
-
# WIP:
|
|
1382
|
+
# WIP: HVAC auto requests (confirmed for Orcon, others?)
|
|
1382
1383
|
def parser_2210(payload: str, msg: Message) -> dict[str, Any]:
|
|
1383
1384
|
try:
|
|
1384
1385
|
assert msg.verb in (RP, I_) or payload == "00"
|
|
1385
|
-
assert payload[10:12] == payload[38:40]
|
|
1386
|
-
10:12
|
|
1387
|
-
|
|
1388
|
-
"58",
|
|
1389
|
-
"64",
|
|
1390
|
-
"96",
|
|
1391
|
-
"FF",
|
|
1392
|
-
), f"expected req.speed? (58|64|96|FF), not {payload[10:12]}"
|
|
1386
|
+
assert payload[10:12] == payload[38:40], (
|
|
1387
|
+
f"expected byte 19 {payload[10:12]}, not {payload[38:40]}"
|
|
1388
|
+
) # auto requested fan speed %. Identical [38:40] is for supply?
|
|
1393
1389
|
assert payload[20:22] == payload[48:50] and payload[20:22] in (
|
|
1394
|
-
"00",
|
|
1395
|
-
"
|
|
1396
|
-
|
|
1390
|
+
"00", # idle
|
|
1391
|
+
"02", # requested by CO2 level/sensor
|
|
1392
|
+
"03", # requested by humidity level/sensor
|
|
1393
|
+
), f"expected req_reason (00|02|03), not {payload[20:22]}"
|
|
1397
1394
|
assert payload[78:80] in ("00", "02"), (
|
|
1398
1395
|
f"expected byte 39 (00|02), not {payload[78:80]}"
|
|
1399
1396
|
)
|
|
@@ -1407,9 +1404,17 @@ def parser_2210(payload: str, msg: Message) -> dict[str, Any]:
|
|
|
1407
1404
|
except AssertionError as err:
|
|
1408
1405
|
_LOGGER.warning(f"{msg!r} < {_INFORM_DEV_MSG} ({err})")
|
|
1409
1406
|
|
|
1407
|
+
_req = "IDL"
|
|
1408
|
+
if payload[20:22] == "02":
|
|
1409
|
+
_req = "CO2"
|
|
1410
|
+
elif payload[20:22] == "03":
|
|
1411
|
+
_req = "HUM"
|
|
1412
|
+
|
|
1410
1413
|
return {
|
|
1411
|
-
|
|
1412
|
-
|
|
1414
|
+
**parse_exhaust_fan_speed(
|
|
1415
|
+
payload[10:12]
|
|
1416
|
+
), # for Orcon: 29 hex == 41 decimal divided by 2 gives 20.5 (%)
|
|
1417
|
+
SZ_REQ_REASON: _req,
|
|
1413
1418
|
"unknown_78": payload[78:80],
|
|
1414
1419
|
"unknown_80": payload[80:82],
|
|
1415
1420
|
"unknown_82": payload[82:],
|
|
@@ -2215,10 +2220,9 @@ def parser_31d9(payload: str, msg: Message) -> dict[str, Any]:
|
|
|
2215
2220
|
# ventilation state (extended), HVAC
|
|
2216
2221
|
def parser_31da(payload: str, msg: Message) -> PayDictT._31DA:
|
|
2217
2222
|
# see: https://github.com/python/typing/issues/1445
|
|
2218
|
-
|
|
2223
|
+
result = {
|
|
2219
2224
|
**parse_exhaust_fan_speed(payload[38:40]), # maybe 31D9[4:6] for some?
|
|
2220
2225
|
**parse_fan_info(payload[36:38]), # 22F3-ish
|
|
2221
|
-
#
|
|
2222
2226
|
**parse_air_quality(payload[2:6]), # 12C8[2:6]
|
|
2223
2227
|
**parse_co2_level(payload[6:10]), # 1298[2:6]
|
|
2224
2228
|
**parse_indoor_humidity(payload[10:12]), # 12A0?
|
|
@@ -2236,6 +2240,11 @@ def parser_31da(payload: str, msg: Message) -> PayDictT._31DA:
|
|
|
2236
2240
|
**parse_supply_flow(payload[50:54]), # NOTE: is supply, not exhaust
|
|
2237
2241
|
**parse_exhaust_flow(payload[54:58]), # NOTE: order switched from others
|
|
2238
2242
|
}
|
|
2243
|
+
if len(payload) == 58:
|
|
2244
|
+
return result # type: ignore[return-value]
|
|
2245
|
+
|
|
2246
|
+
result.update({"_extra": payload[58:]}) # sporadic [58:60] always 00
|
|
2247
|
+
return result # type: ignore[return-value]
|
|
2239
2248
|
|
|
2240
2249
|
# From an Orcon 15RF Display
|
|
2241
2250
|
# 1 Software version
|
ramses_tx/ramses.py
CHANGED
|
@@ -871,11 +871,14 @@ _DEV_KLASSES_HEAT: dict[str, dict[Code, dict[VerbT, Any]]] = {
|
|
|
871
871
|
Code._000A: {RP: {}},
|
|
872
872
|
Code._000C: {RP: {}},
|
|
873
873
|
Code._1FC9: {I_: {}},
|
|
874
|
+
Code._1FD4: {I_: {}}, # Spider Autotemp, slave 'ticker'
|
|
874
875
|
Code._10E0: {I_: {}, RP: {}},
|
|
875
876
|
Code._22C9: {I_: {}}, # NOTE: No RP
|
|
876
877
|
Code._22D0: {I_: {}, RP: {}},
|
|
877
878
|
Code._2309: {RP: {}},
|
|
879
|
+
Code._3110: {I_: {}}, # Spider Autotemp
|
|
878
880
|
Code._3150: {I_: {}},
|
|
881
|
+
Code._4E01: {I_: {}}, # Spider Autotemp Zone controller
|
|
879
882
|
},
|
|
880
883
|
DevType.TRV: { # e.g. HR92/HR91: Radiator Controller
|
|
881
884
|
Code._0001: {W_: {r"^0[0-9A-F]"}},
|
|
@@ -1411,7 +1414,6 @@ _31DA_FAN_INFO: dict[int, str] = {
|
|
|
1411
1414
|
0x1F: "-unknown 0x1F-", # static field, used as filter in parser_31da so keep same
|
|
1412
1415
|
}
|
|
1413
1416
|
|
|
1414
|
-
|
|
1415
1417
|
#
|
|
1416
1418
|
########################################################################################
|
|
1417
1419
|
# CODES_BY_ZONE_TYPE
|
ramses_tx/transport.py
CHANGED
|
@@ -1073,7 +1073,7 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
|
|
|
1073
1073
|
self._connecting = True
|
|
1074
1074
|
try:
|
|
1075
1075
|
self.client.connect_async(
|
|
1076
|
-
self._broker_url.hostname
|
|
1076
|
+
str(self._broker_url.hostname or "localhost"),
|
|
1077
1077
|
self._broker_url.port or 1883,
|
|
1078
1078
|
60,
|
|
1079
1079
|
)
|
|
@@ -1172,10 +1172,18 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
|
|
|
1172
1172
|
self,
|
|
1173
1173
|
client: mqtt.Client,
|
|
1174
1174
|
userdata: Any,
|
|
1175
|
-
|
|
1176
|
-
|
|
1175
|
+
*args: Any,
|
|
1176
|
+
**kwargs: Any,
|
|
1177
1177
|
) -> None:
|
|
1178
|
-
|
|
1178
|
+
# Handle different paho-mqtt callback signatures
|
|
1179
|
+
reason_code = args[0] if len(args) >= 1 else None
|
|
1180
|
+
|
|
1181
|
+
reason_name = (
|
|
1182
|
+
reason_code.getName()
|
|
1183
|
+
if reason_code is not None and hasattr(reason_code, "getName")
|
|
1184
|
+
else str(reason_code)
|
|
1185
|
+
)
|
|
1186
|
+
_LOGGER.warning(f"MQTT disconnected: {reason_name}")
|
|
1179
1187
|
|
|
1180
1188
|
was_connected = self._connected
|
|
1181
1189
|
self._connected = False
|
|
@@ -1191,13 +1199,9 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
|
|
|
1191
1199
|
self._protocol.pause_writing()
|
|
1192
1200
|
|
|
1193
1201
|
# Only attempt reconnection if we didn't deliberately disconnect
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
self._schedule_reconnect()
|
|
1198
|
-
elif reason_code.is_failure and not self._closing:
|
|
1199
|
-
# Connection failed, also schedule reconnection
|
|
1200
|
-
_LOGGER.debug("MQTT connection failed - scheduling reconnection")
|
|
1202
|
+
|
|
1203
|
+
if not self._closing:
|
|
1204
|
+
# Schedule reconnection for any disconnect (unexpected or failure)
|
|
1201
1205
|
self._schedule_reconnect()
|
|
1202
1206
|
|
|
1203
1207
|
def _create_connection(self, msg: mqtt.MQTTMessage) -> None:
|
ramses_tx/typed_dicts.py
CHANGED
|
@@ -143,6 +143,8 @@ class ExhaustFlow(TypedDict):
|
|
|
143
143
|
|
|
144
144
|
|
|
145
145
|
class _VentilationState(
|
|
146
|
+
ExhaustFanSpeed,
|
|
147
|
+
FanInfo,
|
|
146
148
|
AirQuality,
|
|
147
149
|
Co2Level,
|
|
148
150
|
ExhaustTemp,
|
|
@@ -151,8 +153,6 @@ class _VentilationState(
|
|
|
151
153
|
OutdoorTemp,
|
|
152
154
|
Capabilities,
|
|
153
155
|
BypassPosition,
|
|
154
|
-
FanInfo,
|
|
155
|
-
ExhaustFanSpeed,
|
|
156
156
|
SupplyFanSpeed,
|
|
157
157
|
RemainingMins,
|
|
158
158
|
PostHeater,
|
|
@@ -162,6 +162,7 @@ class _VentilationState(
|
|
|
162
162
|
):
|
|
163
163
|
indoor_humidity: _HexToTempT
|
|
164
164
|
outdoor_humidity: _HexToTempT
|
|
165
|
+
extra: NotRequired[str | None]
|
|
165
166
|
|
|
166
167
|
|
|
167
168
|
# These are payload-specific...
|
ramses_tx/version.py
CHANGED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
ramses_cli/__init__.py,sha256=uvGzWqOf4avvgzxJNSLFWEelIWqSZ-AeLAZzg5x58bc,397
|
|
2
|
-
ramses_cli/client.py,sha256=QOmPKjCQHHOZwLBWEB438zabI9k38-ELRwisLvbvxSU,19782
|
|
3
|
-
ramses_cli/debug.py,sha256=vgR0lOHoYjWarN948dI617WZZGNuqHbeq6Tc16Da7b4,608
|
|
4
|
-
ramses_cli/discovery.py,sha256=81XbmpNiCpUHVZBwo2g1eRwyJG-wZhpSsc44G3hHlFA,12972
|
|
5
|
-
ramses_cli/utils/cat_slow.py,sha256=AhUpM5gnegCitNKU-JGHn-DrRzSi-49ZR1Qw6lxe_t8,607
|
|
6
|
-
ramses_cli/utils/convert.py,sha256=D_YiCyX5na9pgC-_NhBlW9N1dgRKUK-uLtLBfofjzZM,1804
|
|
7
|
-
ramses_rf/__init__.py,sha256=zONFBiRdf07cPTSxzr2V3t-b3CGokZjT9SGit4JUKRA,1055
|
|
8
|
-
ramses_rf/binding_fsm.py,sha256=uZAOl3i19KCXqqlaLJWkEqMMP7NJBhVPW3xTikQD1fY,25996
|
|
9
|
-
ramses_rf/const.py,sha256=DSo4ROWDlOlcdXQdrpAF17vOsTLgmf2u0UppjYa5qJI,5390
|
|
10
|
-
ramses_rf/database.py,sha256=6k5MLtK5Lplz8THfluQoQU-eniUkqSwEUMvVW7VyGhI,9880
|
|
11
|
-
ramses_rf/dispatcher.py,sha256=b7Cg1vAP6FECC6GeZsJ0BZVqy-ZjJTXhZquzcwE87WI,11221
|
|
12
|
-
ramses_rf/entity_base.py,sha256=vH4lmwXnylSM-1MWmat0_QRSNVCRi3iVhcqj9O41Pms,39602
|
|
13
|
-
ramses_rf/exceptions.py,sha256=rzVZDcYxFH7BjUAQ3U1fHWtgBpwk3BgjX1TO1L1iM8c,2538
|
|
14
|
-
ramses_rf/gateway.py,sha256=vqoTEb6QXnwaIMa66oed_3LEVvlyQ3flsAAMliEEvVA,20921
|
|
15
|
-
ramses_rf/helpers.py,sha256=LcrVLqnF2qJWqXrC7UXKOQE8khCT3OhoTpZ_ZVBjw3A,4249
|
|
16
|
-
ramses_rf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
ramses_rf/schemas.py,sha256=mYOUZOH5OIDNBxRM2vd8POzDWEEmLhxh5UtqjTpFNek,13287
|
|
18
|
-
ramses_rf/version.py,sha256=zEZhrTC1gMQzcGZ3ZSkZrhF_OBj8Dnlv5VhehgnT3_s,125
|
|
19
|
-
ramses_rf/device/__init__.py,sha256=sUbH5dhbYFXSoM_TPFRutpRutBRpup7_cQ9smPtDTy8,4858
|
|
20
|
-
ramses_rf/device/base.py,sha256=V2YzRhdxrTqfHYrCBq6pJsYdTgAx8gGzfdo8pkbeEo8,17450
|
|
21
|
-
ramses_rf/device/heat.py,sha256=2sCsggySVcuTzyXDmgWy76QbhlU5MQWSejy3zgI5BDE,54242
|
|
22
|
-
ramses_rf/device/hvac.py,sha256=QAlWx6jBArbHzZD5Mm1wZPmIbrQrw4ljbTDV0P4IH3I,23438
|
|
23
|
-
ramses_rf/system/__init__.py,sha256=uZLKio3gLlBzePa2aDQ1nxkcp1YXOGrn6iHTG8LiNIw,711
|
|
24
|
-
ramses_rf/system/faultlog.py,sha256=GdGmVGT3137KsTlV_nhccgIFEmYu6DFsLTn4S-8JSok,12799
|
|
25
|
-
ramses_rf/system/heat.py,sha256=dARzcwL39JGwOBJkKJBi0_i7rr8IvY-qaNmWmgJLpdo,39223
|
|
26
|
-
ramses_rf/system/schedule.py,sha256=Ts6tdZPTQLV5NkgwA73tPa5QUsnZNIIuYoKC-8VsXDk,18808
|
|
27
|
-
ramses_rf/system/zones.py,sha256=QwRtSHY5c-Amcs6JD16uQcimOsEQTZcMm1dW-pqEFqM,36041
|
|
28
|
-
ramses_tx/__init__.py,sha256=wJ7Ntx-0AyJwYwSG8OrFMpxDLXs6GbECbCcYhq98mSA,3162
|
|
29
|
-
ramses_tx/address.py,sha256=2640K3sXzogZtd4-tSxwVjYEEXcFE1DgmtvZlTMM5mE,8444
|
|
30
|
-
ramses_tx/command.py,sha256=g5PBf9JnuygveyaYrqIuV8wIn7grm0evuqKy9Cp1oaA,53844
|
|
31
|
-
ramses_tx/const.py,sha256=B2db8Yxks-lMNsQAK1DoPkF1gvwNIacLmKwXuApUyLk,30221
|
|
32
|
-
ramses_tx/exceptions.py,sha256=FJSU9YkvpKjs3yeTqUJX1o3TPFSe_B01gRGIh9b3PNc,2632
|
|
33
|
-
ramses_tx/fingerprints.py,sha256=nfftA1E62HQnb-eLt2EqjEi_la0DAoT0wt-PtTMie0s,11974
|
|
34
|
-
ramses_tx/frame.py,sha256=9lUVh8gAMXNRAolfFw2WuWANjn24AWkmscuM9Tm5imE,22036
|
|
35
|
-
ramses_tx/gateway.py,sha256=FE5MWA1eIE9JATA2vRoBSQ8fAzqp7TqAm3Ds3k1KnKE,11267
|
|
36
|
-
ramses_tx/helpers.py,sha256=WJ5JtAT9iyhkcW53AIPNPuvGEUWFwLumZc-mCG2kIOc,32236
|
|
37
|
-
ramses_tx/logger.py,sha256=7vUpcfOFMW95juMWDx5dhUOqV8DTsindZ-Qz2aCmEoA,11073
|
|
38
|
-
ramses_tx/message.py,sha256=J1wvVkLPJQr2ffKCUQYSWwLPzRTZBC0zUU5W9DkN3hU,13190
|
|
39
|
-
ramses_tx/opentherm.py,sha256=58PXz9l5x8Ou6Fm3y-R_UnGHCYahoi2RKIDdYStUMzk,42378
|
|
40
|
-
ramses_tx/packet.py,sha256=NGunaGCkEjhTp9t4mARK5e7kbqT-Z_JKCH7ibMYMJXU,7357
|
|
41
|
-
ramses_tx/parsers.py,sha256=eU5dqbbw1vzWDFxDhyNPy2j6t_LQN56mRJa0A-PeKiE,109411
|
|
42
|
-
ramses_tx/protocol.py,sha256=ifj3qwcQivjQDaQUwM94xp-U8Pmef6zwSH7mav8DLWA,28867
|
|
43
|
-
ramses_tx/protocol_fsm.py,sha256=YhHkTqbl8w-myimsOjV50uIFgg9HiApwPU7xA_jg5nU,26827
|
|
44
|
-
ramses_tx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
-
ramses_tx/ramses.py,sha256=GnZwvx-HSVFdjXfUen6aWClDtrAmYaKwbrWl-LsyKO4,52045
|
|
46
|
-
ramses_tx/schemas.py,sha256=h2AcArVROy1_C4n6F0Crj4e-2BxXxH74xogFlc6nKHI,12769
|
|
47
|
-
ramses_tx/transport.py,sha256=-IO8UY85OOytciX3h7tFN58BBDtI3TEoOgmUmv-LiNc,56288
|
|
48
|
-
ramses_tx/typed_dicts.py,sha256=4ZT50M-Cuwy2ljAIorwoxEJ9c737xUHrUxX9wTh79xE,10834
|
|
49
|
-
ramses_tx/typing.py,sha256=eF2SlPWhNhEFQj6WX2AhTXiyRQVXYnFutiepllYl2rI,5042
|
|
50
|
-
ramses_tx/version.py,sha256=QqJQBBdWjuoIvxrhzIH4ysjOp5bzB0KI1TdFhr9c6og,123
|
|
51
|
-
ramses_rf-0.51.4.dist-info/METADATA,sha256=odaoeguG-xZAlWq8nYF4PHVK3SV5mzJRi1QdD4IBKcE,3906
|
|
52
|
-
ramses_rf-0.51.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
53
|
-
ramses_rf-0.51.4.dist-info/entry_points.txt,sha256=NnyK29baOCNg8DinPYiZ368h7MTH7bgTW26z2A1NeIE,50
|
|
54
|
-
ramses_rf-0.51.4.dist-info/licenses/LICENSE,sha256=-Kc35W7l1UkdiQ4314_yVWv7vDDrg7IrJfMLUiq6Nfs,1074
|
|
55
|
-
ramses_rf-0.51.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|