ramses-rf 0.52.3__py3-none-any.whl → 0.52.4__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_rf/database.py +7 -3
- ramses_rf/device/heat.py +1 -1
- ramses_rf/entity_base.py +67 -26
- ramses_rf/gateway.py +9 -9
- ramses_rf/system/heat.py +1 -1
- ramses_rf/version.py +1 -1
- {ramses_rf-0.52.3.dist-info → ramses_rf-0.52.4.dist-info}/METADATA +2 -1
- {ramses_rf-0.52.3.dist-info → ramses_rf-0.52.4.dist-info}/RECORD +17 -17
- ramses_tx/const.py +1 -1
- ramses_tx/parsers.py +57 -12
- ramses_tx/protocol.py +2 -2
- ramses_tx/ramses.py +9 -5
- ramses_tx/transport.py +1 -0
- ramses_tx/version.py +1 -1
- {ramses_rf-0.52.3.dist-info → ramses_rf-0.52.4.dist-info}/WHEEL +0 -0
- {ramses_rf-0.52.3.dist-info → ramses_rf-0.52.4.dist-info}/entry_points.txt +0 -0
- {ramses_rf-0.52.3.dist-info → ramses_rf-0.52.4.dist-info}/licenses/LICENSE +0 -0
ramses_rf/database.py
CHANGED
|
@@ -130,11 +130,15 @@ class MessageIndex:
|
|
|
130
130
|
def stop(self) -> None:
|
|
131
131
|
"""Stop the housekeeper loop."""
|
|
132
132
|
|
|
133
|
-
if
|
|
133
|
+
if (
|
|
134
|
+
self.maintain
|
|
135
|
+
and self._housekeeping_task
|
|
136
|
+
and (not self._housekeeping_task.done())
|
|
137
|
+
):
|
|
134
138
|
self._housekeeping_task.cancel() # stop the housekeeper
|
|
135
139
|
|
|
136
140
|
self._cx.commit() # just in case
|
|
137
|
-
|
|
141
|
+
self._cx.close() # may still need to do queries after engine has stopped?
|
|
138
142
|
|
|
139
143
|
@property
|
|
140
144
|
def msgs(self) -> MsgDdT:
|
|
@@ -193,7 +197,7 @@ class MessageIndex:
|
|
|
193
197
|
"""
|
|
194
198
|
dtm = dt_now - _cutoff # .isoformat(timespec="microseconds") < needed?
|
|
195
199
|
|
|
196
|
-
self._cu.execute("SELECT dtm FROM messages WHERE dtm
|
|
200
|
+
self._cu.execute("SELECT dtm FROM messages WHERE dtm >= ?", (dtm,))
|
|
197
201
|
rows = self._cu.fetchall() # fetch dtm of current messages to retain
|
|
198
202
|
|
|
199
203
|
try: # make this operation atomic, i.e. update self._msgs only on success
|
ramses_rf/device/heat.py
CHANGED
|
@@ -450,7 +450,7 @@ class UfhController(Parent, DeviceHeat): # UFC (02):
|
|
|
450
450
|
# )
|
|
451
451
|
# self._send_cmd(cmd)
|
|
452
452
|
|
|
453
|
-
elif msg.code == Code._0008: # relay_demand
|
|
453
|
+
elif msg.code == Code._0008: # relay_demand
|
|
454
454
|
if msg.payload.get(SZ_DOMAIN_ID) == FC:
|
|
455
455
|
self._relay_demand = msg
|
|
456
456
|
else: # FA
|
ramses_rf/entity_base.py
CHANGED
|
@@ -224,7 +224,10 @@ class _MessageDB(_Entity):
|
|
|
224
224
|
|
|
225
225
|
# As of 0.52.1 we use SQLite MessageIndex, see ramses_rf/database.py
|
|
226
226
|
# _msgz_ (nested) was only used in this module. Note:
|
|
227
|
-
# _msgz (now rebuilt from _msgs) also used in:
|
|
227
|
+
# _msgz (now rebuilt from _msgs) is also used in:
|
|
228
|
+
# - client.py: for code in device._msgz.values()
|
|
229
|
+
# - base.py: Code._1060 in self._msgz
|
|
230
|
+
# [x] device.heat (no longer used)
|
|
228
231
|
|
|
229
232
|
def _handle_msg(self, msg: Message) -> None:
|
|
230
233
|
"""Store a msg in the DBs.
|
|
@@ -244,14 +247,14 @@ class _MessageDB(_Entity):
|
|
|
244
247
|
|
|
245
248
|
if self._gwy.msg_db: # central SQLite MessageIndex
|
|
246
249
|
_LOGGER.debug(
|
|
247
|
-
"For %s (
|
|
250
|
+
"For %s (_z_id %s) add msg %s, src %s, dst %s to msg_db.",
|
|
248
251
|
self.id,
|
|
249
252
|
self._z_id,
|
|
250
253
|
msg,
|
|
251
254
|
msg.src,
|
|
252
255
|
msg.dst,
|
|
253
256
|
)
|
|
254
|
-
debug_code: Code = Code._3150
|
|
257
|
+
debug_code: Code = Code._3150 # for debugging only log these, pick your own
|
|
255
258
|
if msg.code == debug_code and msg.src.id.startswith("01:"):
|
|
256
259
|
_LOGGER.debug(
|
|
257
260
|
"Added msg from %s with code %s to _gwy.msg_db. hdr=%s",
|
|
@@ -293,6 +296,7 @@ class _MessageDB(_Entity):
|
|
|
293
296
|
def _msg_list(self) -> list[Message]:
|
|
294
297
|
"""Return a flattened list of all messages logged on this device."""
|
|
295
298
|
# (only) used in gateway.py#get_state() and in tests/tests/test_eavesdrop_schema.py
|
|
299
|
+
# TODO remove _msg_list Q1 2026
|
|
296
300
|
if self._gwy.msg_db:
|
|
297
301
|
msg_list_qry: list[Message] = []
|
|
298
302
|
code_list = self._msg_dev_qry()
|
|
@@ -303,16 +307,21 @@ class _MessageDB(_Entity):
|
|
|
303
307
|
msg_list_qry.append(self._msgs[c])
|
|
304
308
|
else:
|
|
305
309
|
# evohome has these errors
|
|
306
|
-
# _msg_list could not fetch self._msgs[7FFF] for 18:072981 (
|
|
310
|
+
# _msg_list could not fetch self._msgs[7FFF] for 18:072981 (_z_id 18:072981)
|
|
307
311
|
_LOGGER.debug(
|
|
308
|
-
"_msg_list could not fetch self._msgs[%s] for %s (
|
|
312
|
+
"_msg_list could not fetch self._msgs[%s] for %s (_z_id %s)",
|
|
309
313
|
c,
|
|
310
314
|
self.id,
|
|
311
315
|
self._z_id,
|
|
312
316
|
)
|
|
313
317
|
return msg_list_qry
|
|
314
318
|
# else create from legacy nested dict
|
|
315
|
-
return [
|
|
319
|
+
return [
|
|
320
|
+
msg
|
|
321
|
+
for code in self._msgz.values()
|
|
322
|
+
for ctx in code.values()
|
|
323
|
+
for msg in ctx.values()
|
|
324
|
+
]
|
|
316
325
|
|
|
317
326
|
def _add_record(
|
|
318
327
|
self, address: Address, code: Code | None = None, verb: str = " I"
|
|
@@ -424,7 +433,7 @@ class _MessageDB(_Entity):
|
|
|
424
433
|
**kwargs: Any,
|
|
425
434
|
) -> dict | list | None:
|
|
426
435
|
"""
|
|
427
|
-
Query the message dict or the SQLite
|
|
436
|
+
Query the _msgz message dict or the SQLite MessageIndex for the most recent
|
|
428
437
|
key: value pairs(s) for a given code.
|
|
429
438
|
|
|
430
439
|
:param code: filter messages by Code or a tuple of Codes, optional
|
|
@@ -517,7 +526,7 @@ class _MessageDB(_Entity):
|
|
|
517
526
|
or (idx == SZ_DOMAIN_ID)
|
|
518
527
|
), (
|
|
519
528
|
f"full dict:{msg_dict}, payload:{msg.payload} < Coding error: key='{idx}', val='{val}'"
|
|
520
|
-
) # should not be there
|
|
529
|
+
) # should not be there
|
|
521
530
|
|
|
522
531
|
if (
|
|
523
532
|
key == "*" or not key
|
|
@@ -535,21 +544,35 @@ class _MessageDB(_Entity):
|
|
|
535
544
|
"""
|
|
536
545
|
Retrieve from the MessageIndex a list of Code keys involving this device.
|
|
537
546
|
|
|
538
|
-
:return: list of Codes or empty list when query returned empty
|
|
547
|
+
:return: list of Codes or an empty list when the query returned empty
|
|
539
548
|
"""
|
|
540
549
|
|
|
541
550
|
if self._gwy.msg_db:
|
|
542
551
|
# SQLite query on MessageIndex
|
|
543
552
|
res: list[Code] = []
|
|
544
|
-
|
|
553
|
+
|
|
554
|
+
if len(self.id) == 9:
|
|
555
|
+
# fetch a ctl's message codes (add all its children?)
|
|
556
|
+
sql = """
|
|
557
|
+
SELECT code from messages WHERE
|
|
558
|
+
verb in (' I', 'RP')
|
|
559
|
+
AND (src = ? OR dst = ?)
|
|
560
|
+
AND ctx LIKE ?
|
|
561
|
+
"""
|
|
562
|
+
_ctx_qry = "%"
|
|
563
|
+
|
|
564
|
+
elif self.id[_ID_SLICE:] == "_HW":
|
|
565
|
+
# fetch a DHW entity's message codes
|
|
545
566
|
sql = """
|
|
546
567
|
SELECT code from messages WHERE
|
|
547
568
|
verb in (' I', 'RP')
|
|
548
569
|
AND (src = ? OR dst = ?)
|
|
549
570
|
AND (ctx IN ('FC', 'FA', 'F9', 'FA') OR plk LIKE ?)
|
|
550
571
|
"""
|
|
551
|
-
_ctx_qry = "%dhw_idx%"
|
|
572
|
+
_ctx_qry = "%dhw_idx%"
|
|
573
|
+
|
|
552
574
|
else:
|
|
575
|
+
# fetch a zone's message codes
|
|
553
576
|
sql = """
|
|
554
577
|
SELECT code from messages WHERE
|
|
555
578
|
verb in (' I', 'RP')
|
|
@@ -562,12 +585,12 @@ class _MessageDB(_Entity):
|
|
|
562
585
|
sql, (self.id[:_ID_SLICE], self.id[:_ID_SLICE], _ctx_qry)
|
|
563
586
|
):
|
|
564
587
|
_LOGGER.debug(
|
|
565
|
-
"Fetched from index: %s for %s (
|
|
588
|
+
"Fetched from index: %s for %s (_z_id %s)",
|
|
566
589
|
rec[0],
|
|
567
590
|
self.id,
|
|
568
591
|
self._z_id,
|
|
569
592
|
)
|
|
570
|
-
# Example: "Fetched from index: code 1FD4 for 01:123456 (
|
|
593
|
+
# Example: "Fetched from index: code 1FD4 for 01:123456 (_z_id 01)"
|
|
571
594
|
res.append(Code(str(rec[0])))
|
|
572
595
|
return res
|
|
573
596
|
else:
|
|
@@ -743,7 +766,18 @@ class _MessageDB(_Entity):
|
|
|
743
766
|
# for r in results:
|
|
744
767
|
# print(r)
|
|
745
768
|
|
|
746
|
-
if self.id
|
|
769
|
+
if len(self.id) == 9:
|
|
770
|
+
# fetch a ctl's message dtms (add all its children?)
|
|
771
|
+
sql = """
|
|
772
|
+
SELECT dtm from messages WHERE
|
|
773
|
+
verb in (' I', 'RP')
|
|
774
|
+
AND (src = ? OR dst = ?)
|
|
775
|
+
AND ctx LIKE ?
|
|
776
|
+
"""
|
|
777
|
+
_ctx_qry = "%"
|
|
778
|
+
|
|
779
|
+
elif self.id[_ID_SLICE:] == "_HW":
|
|
780
|
+
# fetch a DHW entity's message dtms
|
|
747
781
|
sql = """
|
|
748
782
|
SELECT dtm from messages WHERE
|
|
749
783
|
verb in (' I', 'RP')
|
|
@@ -753,6 +787,7 @@ class _MessageDB(_Entity):
|
|
|
753
787
|
_ctx_qry = "%dhw_idx%"
|
|
754
788
|
# TODO add Children messages? self.ctl.dhw
|
|
755
789
|
else:
|
|
790
|
+
# fetch a zone's message dtms
|
|
756
791
|
sql = """
|
|
757
792
|
SELECT dtm from messages WHERE
|
|
758
793
|
verb in (' I', 'RP')
|
|
@@ -769,14 +804,14 @@ class _MessageDB(_Entity):
|
|
|
769
804
|
}
|
|
770
805
|
# if CTL, remove 3150, 3220 heat_demand, both are only stored on children
|
|
771
806
|
# HACK
|
|
772
|
-
if self.id[:3] == "01:" and self._SLUG == "CTL":
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
807
|
+
# if self.id[:3] == "01:" and self._SLUG == "CTL":
|
|
808
|
+
# with next ON: 2 errors , both 1x UFC, 1x CTR
|
|
809
|
+
# with next OFF: 4 errors, all CTR
|
|
810
|
+
# if Code._3150 in _msg_dict: # Note: CTL can send a 3150 (see heat_ufc_00)
|
|
811
|
+
# _msg_dict.pop(Code._3150) # keep, prefer to have 2 extra instead of missing 1
|
|
812
|
+
# if Code._3220 in _msg_dict:
|
|
813
|
+
# _msg_dict.pop(Code._3220)
|
|
814
|
+
# _LOGGER.debug(f"Removed 3150/3220 from %s._msgs dict", self.id)
|
|
780
815
|
return _msg_dict
|
|
781
816
|
|
|
782
817
|
@property
|
|
@@ -862,7 +897,6 @@ class _Discovery(_MessageDB):
|
|
|
862
897
|
# return f"{data_id:02X}" # type: ignore[return-value]
|
|
863
898
|
|
|
864
899
|
res: list[str] = []
|
|
865
|
-
# look for the "sim" OT 3220 record initially added in OtbGateway.init
|
|
866
900
|
if self._gwy.msg_db:
|
|
867
901
|
# SQLite query for ctx field on MessageIndex
|
|
868
902
|
sql = """
|
|
@@ -995,18 +1029,24 @@ class _Discovery(_MessageDB):
|
|
|
995
1029
|
sql = """
|
|
996
1030
|
SELECT dtm from messages WHERE
|
|
997
1031
|
code = ?
|
|
998
|
-
verb = ' I'
|
|
1032
|
+
AND verb = ' I'
|
|
999
1033
|
AND ctx = 'True'
|
|
1000
1034
|
AND (src = ? OR dst = ?)
|
|
1001
1035
|
"""
|
|
1002
|
-
|
|
1036
|
+
res = self._gwy.msg_db.qry(
|
|
1003
1037
|
sql,
|
|
1004
1038
|
(
|
|
1005
1039
|
task[_SZ_COMMAND].code,
|
|
1006
1040
|
self.tcs.id[:_ID_SLICE],
|
|
1007
1041
|
self.tcs.id[:_ID_SLICE],
|
|
1008
1042
|
),
|
|
1009
|
-
)
|
|
1043
|
+
)
|
|
1044
|
+
if len(res) > 0:
|
|
1045
|
+
msgs += res[0] # expect 1 Message in returned tuple
|
|
1046
|
+
else:
|
|
1047
|
+
_LOGGER.debug(
|
|
1048
|
+
f"No msg found for hdr {hdr}, tesk code {task[_SZ_COMMAND].code}"
|
|
1049
|
+
)
|
|
1010
1050
|
else: # TODO(eb) remove next Q1 2026
|
|
1011
1051
|
msgs += [self.tcs._msgz[task[_SZ_COMMAND].code][I_][True]]
|
|
1012
1052
|
# raise NotImplementedError
|
|
@@ -1145,6 +1185,7 @@ class Parent(Entity): # A System, Zone, DhwZone or a UfhController
|
|
|
1145
1185
|
(incl. the DHW Zone), and also any UFH controllers.
|
|
1146
1186
|
|
|
1147
1187
|
For a heating Zone, children are limited to a sensor, and a number of actuators.
|
|
1188
|
+
|
|
1148
1189
|
For the DHW Zone, the children are limited to a sensor, a DHW valve, and/or a
|
|
1149
1190
|
heating valve.
|
|
1150
1191
|
|
ramses_rf/gateway.py
CHANGED
|
@@ -258,14 +258,6 @@ class Gateway(Engine):
|
|
|
258
258
|
# return True
|
|
259
259
|
return include_expired or not msg._expired
|
|
260
260
|
|
|
261
|
-
msgs = [m for device in self.devices for m in device._msg_list]
|
|
262
|
-
|
|
263
|
-
for system in self.systems:
|
|
264
|
-
msgs.extend(list(system._msgs.values()))
|
|
265
|
-
msgs.extend([m for z in system.zones for m in z._msgs.values()])
|
|
266
|
-
# msgs.extend([m for z in system.dhw for m in z._msgs.values()]) # TODO: DHW
|
|
267
|
-
# Related to/Fixes ramses_cc Issue 249 non-existing via-device _HW ?
|
|
268
|
-
|
|
269
261
|
if self.msg_db:
|
|
270
262
|
pkts = {
|
|
271
263
|
f"{repr(msg._pkt)[:26]}": f"{repr(msg._pkt)[27:]}"
|
|
@@ -273,12 +265,20 @@ class Gateway(Engine):
|
|
|
273
265
|
if wanted_msg(msg, include_expired=include_expired)
|
|
274
266
|
}
|
|
275
267
|
else: # deprecated, to be removed in Q1 2026
|
|
276
|
-
|
|
268
|
+
msgs = [m for device in self.devices for m in device._msg_list]
|
|
269
|
+
# add systems._msgs and zones._msgs
|
|
270
|
+
for system in self.systems:
|
|
271
|
+
msgs.extend(list(system._msgs.values()))
|
|
272
|
+
msgs.extend([m for z in system.zones for m in z._msgs.values()])
|
|
273
|
+
# msgs.extend([m for z in system.dhw for m in z._msgs.values()]) # TODO: DHW
|
|
274
|
+
# Related to/Fixes ramses_cc Issue 249 non-existing via-device _HW ?
|
|
275
|
+
|
|
277
276
|
pkts = { # BUG: assumes pkts have unique dtms: may be untrue for contrived logs
|
|
278
277
|
f"{repr(msg._pkt)[:26]}": f"{repr(msg._pkt)[27:]}"
|
|
279
278
|
for msg in msgs
|
|
280
279
|
if wanted_msg(msg, include_expired=include_expired)
|
|
281
280
|
}
|
|
281
|
+
# _LOGGER.warning("Missing MessageIndex")
|
|
282
282
|
|
|
283
283
|
self._resume()
|
|
284
284
|
|
ramses_rf/system/heat.py
CHANGED
|
@@ -532,7 +532,7 @@ class MultiZone(SystemBase): # 0005 (+/- 000C?)
|
|
|
532
532
|
schema = shrink(SCH_TCS_ZONES_ZON(schema))
|
|
533
533
|
|
|
534
534
|
zon: Zone = self.zone_by_idx.get(zone_idx) # type: ignore[assignment]
|
|
535
|
-
if zon is None:
|
|
535
|
+
if zon is None: # not found in tcs, create it
|
|
536
536
|
zon = zone_factory(self, zone_idx, msg=msg, **schema) # type: ignore[unreachable]
|
|
537
537
|
self.zone_by_idx[zon.idx] = zon
|
|
538
538
|
self.zones.append(zon)
|
ramses_rf/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ramses_rf
|
|
3
|
-
Version: 0.52.
|
|
3
|
+
Version: 0.52.4
|
|
4
4
|
Summary: A stateful RAMSES-II protocol decoder & analyser.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ramses-rf/ramses_rf
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/ramses-rf/ramses_rf/issues
|
|
@@ -22,6 +22,7 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|

|
|
23
23
|

|
|
24
24
|

|
|
25
|
+
[](https://github.com/ramses-rf/ramses_rf/actions/workflows/check-cov.yml)
|
|
25
26
|
|
|
26
27
|
## Overview
|
|
27
28
|
|
|
@@ -7,28 +7,28 @@ ramses_cli/utils/convert.py,sha256=D_YiCyX5na9pgC-_NhBlW9N1dgRKUK-uLtLBfofjzZM,1
|
|
|
7
7
|
ramses_rf/__init__.py,sha256=vp2TyFGqc1fGQHsevhmaw0QEmSSCnZx7fqizKiEwHtw,1245
|
|
8
8
|
ramses_rf/binding_fsm.py,sha256=fuqvcc9YW-wr8SPH8zadpPqrHAvzl_eeWF-IBtlLppY,26632
|
|
9
9
|
ramses_rf/const.py,sha256=L3z31CZ-xqno6oZp_h-67CB_5tDDqTwSWXsqRtsjMcs,5460
|
|
10
|
-
ramses_rf/database.py,sha256=
|
|
10
|
+
ramses_rf/database.py,sha256=2sHiBhI5121HdX-IMquY3h5qQzDljeGWpQ9x6U4duEo,20408
|
|
11
11
|
ramses_rf/dispatcher.py,sha256=YjEU-QrBLo9IfoEhJo2ikg_FxOaMYoWvzelr9Vi-JZ8,11398
|
|
12
|
-
ramses_rf/entity_base.py,sha256=
|
|
12
|
+
ramses_rf/entity_base.py,sha256=LifFLcxI867j4DmsaNSHsVl2fUEPkXpJdz4b7ZFpupo,58929
|
|
13
13
|
ramses_rf/exceptions.py,sha256=mt_T7irqHSDKir6KLaf6oDglUIdrw0S40JbOrWJk5jc,3657
|
|
14
|
-
ramses_rf/gateway.py,sha256=
|
|
14
|
+
ramses_rf/gateway.py,sha256=c88_HCAEBYPaNETMC0IUK1HU9QuEOnCGnK-5wze08_s,21289
|
|
15
15
|
ramses_rf/helpers.py,sha256=TNk_QkpIOB3alOp1sqnA9LOzi4fuDCeapNlW3zTzNas,4250
|
|
16
16
|
ramses_rf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
ramses_rf/schemas.py,sha256=jvpx0hZiMhaogyxbxnrbxS7m2GekKK4DFfVTNs7h-WQ,13476
|
|
18
|
-
ramses_rf/version.py,sha256=
|
|
18
|
+
ramses_rf/version.py,sha256=9IsbhdNecTxEJDHwnSjdsa0r2hpABWxeNcD6LfKKNs0,125
|
|
19
19
|
ramses_rf/device/__init__.py,sha256=sUbH5dhbYFXSoM_TPFRutpRutBRpup7_cQ9smPtDTy8,4858
|
|
20
20
|
ramses_rf/device/base.py,sha256=Yx0LZwMEb49naY8FolZ8HEBFb6XCPQBTVN_TWyO2nKg,17777
|
|
21
|
-
ramses_rf/device/heat.py,sha256=
|
|
21
|
+
ramses_rf/device/heat.py,sha256=I9_NlB_cOxCfCb3Zx88hY6wd7yIrknzo6msrWxXeeEs,54545
|
|
22
22
|
ramses_rf/device/hvac.py,sha256=gdpVACUvtq6ERMI0mwRhqIJtKYEmybzJA2NeyN1ELrs,48431
|
|
23
23
|
ramses_rf/system/__init__.py,sha256=uZLKio3gLlBzePa2aDQ1nxkcp1YXOGrn6iHTG8LiNIw,711
|
|
24
24
|
ramses_rf/system/faultlog.py,sha256=GdGmVGT3137KsTlV_nhccgIFEmYu6DFsLTn4S-8JSok,12799
|
|
25
|
-
ramses_rf/system/heat.py,sha256=
|
|
25
|
+
ramses_rf/system/heat.py,sha256=qQmzgmyHy2x87gHAstn0ee7ZVVOq-GJIfDxCrC-6gFU,39254
|
|
26
26
|
ramses_rf/system/schedule.py,sha256=Ts6tdZPTQLV5NkgwA73tPa5QUsnZNIIuYoKC-8VsXDk,18808
|
|
27
27
|
ramses_rf/system/zones.py,sha256=YN_HAbeaa2YioUOjafEpp-0IHmIwmKNSK_77pPcjtns,36072
|
|
28
28
|
ramses_tx/__init__.py,sha256=sqnjM7pUGJDmec6igTtKViSB8FLX49B5gwhAmcY9ERY,3596
|
|
29
29
|
ramses_tx/address.py,sha256=F5ZE-EbPNNom1fW9XXUILvD7DYSMBxNJvsHVliT5gjw,8452
|
|
30
30
|
ramses_tx/command.py,sha256=tqVECwd_QokEcRv2MSk7TUU4JSBzCZcJh1eQ0jIGgoY,125122
|
|
31
|
-
ramses_tx/const.py,sha256=
|
|
31
|
+
ramses_tx/const.py,sha256=AMwHitDq115rB24f3fzclNGC4ArMW16DbqiFWQc0U5o,30306
|
|
32
32
|
ramses_tx/exceptions.py,sha256=FJSU9YkvpKjs3yeTqUJX1o3TPFSe_B01gRGIh9b3PNc,2632
|
|
33
33
|
ramses_tx/fingerprints.py,sha256=nfftA1E62HQnb-eLt2EqjEi_la0DAoT0wt-PtTMie0s,11974
|
|
34
34
|
ramses_tx/frame.py,sha256=GzNsXr15YLeidJYGtk_xPqsZQh4ehDDlUCtT6rTDhT8,22046
|
|
@@ -38,18 +38,18 @@ ramses_tx/logger.py,sha256=1iKRHKUaqHqGd76CkE_6mCVR0sYODtxshRRwfY61fTk,10426
|
|
|
38
38
|
ramses_tx/message.py,sha256=-moQ8v3HVlNSl-x3U0DDfDcj8WQ7vLqclMNxsohbmnw,13449
|
|
39
39
|
ramses_tx/opentherm.py,sha256=58PXz9l5x8Ou6Fm3y-R_UnGHCYahoi2RKIDdYStUMzk,42378
|
|
40
40
|
ramses_tx/packet.py,sha256=_qHiPFWpQpKueZOgf1jJ93Y09iZjo3LZWStLglVkXg4,7370
|
|
41
|
-
ramses_tx/parsers.py,sha256=
|
|
42
|
-
ramses_tx/protocol.py,sha256=
|
|
41
|
+
ramses_tx/parsers.py,sha256=xkffUleahvY6uPb34pcBDYnJ7QNQUzRuaED7wOVeEsE,113466
|
|
42
|
+
ramses_tx/protocol.py,sha256=zjOz0TCzMmbUiLouLFXKRL7u9a6p9YsCkLYDVC5vG-Y,28867
|
|
43
43
|
ramses_tx/protocol_fsm.py,sha256=ZKtehCr_4TaDdfdlfidFLJaOVTYtaEq5h4tLqNIhb9s,26827
|
|
44
44
|
ramses_tx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
-
ramses_tx/ramses.py,sha256=
|
|
45
|
+
ramses_tx/ramses.py,sha256=vp748Tf_a-56OMM8CWDA2ZktRfTuj0QVyPRcnsOstSM,53983
|
|
46
46
|
ramses_tx/schemas.py,sha256=bqKW_V0bR6VbBD8ZQiBExNtVdXs0fryVKe3GEhupgIo,13424
|
|
47
|
-
ramses_tx/transport.py,sha256=
|
|
47
|
+
ramses_tx/transport.py,sha256=8U6Hskle_0tdI4WViJT0e9Bl6-OeK1uKBvTQZvHtqyY,58923
|
|
48
48
|
ramses_tx/typed_dicts.py,sha256=w-0V5t2Q3GiNUOrRAWiW9GtSwbta_7luME6GfIb1zhI,10869
|
|
49
49
|
ramses_tx/typing.py,sha256=eF2SlPWhNhEFQj6WX2AhTXiyRQVXYnFutiepllYl2rI,5042
|
|
50
|
-
ramses_tx/version.py,sha256=
|
|
51
|
-
ramses_rf-0.52.
|
|
52
|
-
ramses_rf-0.52.
|
|
53
|
-
ramses_rf-0.52.
|
|
54
|
-
ramses_rf-0.52.
|
|
55
|
-
ramses_rf-0.52.
|
|
50
|
+
ramses_tx/version.py,sha256=UqvJQixbILsFY_XbawFKzLgN4gL1DKFAtz6JsZAXk4I,123
|
|
51
|
+
ramses_rf-0.52.4.dist-info/METADATA,sha256=XTTcXOovtegIupIA9b5wQPCfRjUeuqtHRMOKKd_5bPo,4179
|
|
52
|
+
ramses_rf-0.52.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
53
|
+
ramses_rf-0.52.4.dist-info/entry_points.txt,sha256=NnyK29baOCNg8DinPYiZ368h7MTH7bgTW26z2A1NeIE,50
|
|
54
|
+
ramses_rf-0.52.4.dist-info/licenses/LICENSE,sha256=-Kc35W7l1UkdiQ4314_yVWv7vDDrg7IrJfMLUiq6Nfs,1074
|
|
55
|
+
ramses_rf-0.52.4.dist-info/RECORD,,
|
ramses_tx/const.py
CHANGED
|
@@ -440,7 +440,7 @@ DEV_TYPE_MAP = attr_dict_factory(
|
|
|
440
440
|
), # CH/DHW devices instead of HVAC/other
|
|
441
441
|
"HEAT_ZONE_SENSORS": ("00", "01", "03", "04", "12", "22", "34"),
|
|
442
442
|
"HEAT_ZONE_ACTUATORS": ("00", "02", "04", "13"),
|
|
443
|
-
"THM_DEVICES": ("03", "12", "22", "34"),
|
|
443
|
+
"THM_DEVICES": ("03", "12", "21", "22", "34"),
|
|
444
444
|
"TRV_DEVICES": ("00", "04"),
|
|
445
445
|
"CONTROLLERS": ("01", "02", "12", "22", "23", "34"), # potentially controllers
|
|
446
446
|
"PROMOTABLE_SLUGS": (DevType.DEV, DevType.HEA, DevType.HVC),
|
ramses_tx/parsers.py
CHANGED
|
@@ -1912,6 +1912,7 @@ def parser_2411(payload: str, msg: Message) -> dict[str, Any]:
|
|
|
1912
1912
|
"01": (2, centile), # 52 (0.0-25.0) (%)
|
|
1913
1913
|
"0F": (2, hex_to_percent), # xx (0.0-1.0) (%)
|
|
1914
1914
|
"10": (4, counter), # 31 (0-1800) (days)
|
|
1915
|
+
# "20": (4, counter), # unknown data type, uncomment when we have more info
|
|
1915
1916
|
"92": (4, hex_to_temp), # 75 (0-30) (C)
|
|
1916
1917
|
} # TODO: _2411_TYPES.get(payload[8:10], (8, no_op))
|
|
1917
1918
|
|
|
@@ -1938,11 +1939,35 @@ def parser_2411(payload: str, msg: Message) -> dict[str, Any]:
|
|
|
1938
1939
|
return result
|
|
1939
1940
|
|
|
1940
1941
|
try:
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1942
|
+
# Handle unknown data types gracefully instead of asserting
|
|
1943
|
+
if payload[8:10] not in _2411_DATA_TYPES:
|
|
1944
|
+
warningmsg = (
|
|
1945
|
+
f"{msg!r} < {_INFORM_DEV_MSG} (param {param_id} has unknown data_type: {payload[8:10]}). "
|
|
1946
|
+
f"This parameter uses an unrecognized data type. "
|
|
1947
|
+
f"Please report this packet and any context about what changed on your system."
|
|
1948
|
+
)
|
|
1949
|
+
# Return partial result with raw hex values for unknown data types
|
|
1950
|
+
if msg.len == 9:
|
|
1951
|
+
result |= {
|
|
1952
|
+
"value": f"0x{payload[10:18]}", # Raw hex value
|
|
1953
|
+
"_value_06": payload[6:10],
|
|
1954
|
+
"_unknown_data_type": payload[8:10],
|
|
1955
|
+
}
|
|
1956
|
+
else:
|
|
1957
|
+
result |= {
|
|
1958
|
+
"value": f"0x{payload[10:18]}", # Raw hex value
|
|
1959
|
+
"_value_06": payload[6:10],
|
|
1960
|
+
"min_value": f"0x{payload[18:26]}", # Raw hex value
|
|
1961
|
+
"max_value": f"0x{payload[26:34]}", # Raw hex value
|
|
1962
|
+
"precision": f"0x{payload[34:42]}", # Raw hex value
|
|
1963
|
+
"_value_42": payload[42:],
|
|
1964
|
+
# Flexible footer - capture everything after precision
|
|
1965
|
+
}
|
|
1966
|
+
_LOGGER.warning(f"{warningmsg}. Found values: {result}")
|
|
1967
|
+
return result
|
|
1945
1968
|
|
|
1969
|
+
# Handle known data types normally
|
|
1970
|
+
length, parser = _2411_DATA_TYPES[payload[8:10]]
|
|
1946
1971
|
result |= {
|
|
1947
1972
|
"value": parser(payload[10:18][-length:]), # type: ignore[operator]
|
|
1948
1973
|
"_value_06": payload[6:10],
|
|
@@ -1962,10 +1987,11 @@ def parser_2411(payload: str, msg: Message) -> dict[str, Any]:
|
|
|
1962
1987
|
# eg. older Orcon models may have a footer of 2 bytes
|
|
1963
1988
|
}
|
|
1964
1989
|
)
|
|
1965
|
-
except
|
|
1966
|
-
_LOGGER.warning(f"{msg!r} < {_INFORM_DEV_MSG} ({err})")
|
|
1967
|
-
# Return partial result for
|
|
1968
|
-
result["value"] = ""
|
|
1990
|
+
except Exception as err:
|
|
1991
|
+
_LOGGER.warning(f"{msg!r} < {_INFORM_DEV_MSG} (Error parsing 2411: {err})")
|
|
1992
|
+
# Return partial result for any parsing errors
|
|
1993
|
+
result["value"] = f"0x{payload[10:18]}" # Raw hex value
|
|
1994
|
+
result["_parse_error"] = f"Parser error: {err}"
|
|
1969
1995
|
return result
|
|
1970
1996
|
|
|
1971
1997
|
|
|
@@ -2981,7 +3007,11 @@ def parser_unknown(payload: str, msg: Message) -> dict[str, Any]:
|
|
|
2981
3007
|
"_value": hex_to_temp(payload[2:]),
|
|
2982
3008
|
}
|
|
2983
3009
|
|
|
2984
|
-
|
|
3010
|
+
return {
|
|
3011
|
+
"_payload": payload,
|
|
3012
|
+
"_unknown_code": msg.code,
|
|
3013
|
+
"_parse_error": "No parser available for this packet type",
|
|
3014
|
+
}
|
|
2985
3015
|
|
|
2986
3016
|
|
|
2987
3017
|
_PAYLOAD_PARSERS = {
|
|
@@ -2998,8 +3028,23 @@ def parse_payload(msg: Message) -> dict | list[dict]:
|
|
|
2998
3028
|
:return: a dict of key: value pairs or a list of such dicts, e.g. {'temperature': 21.5}
|
|
2999
3029
|
"""
|
|
3000
3030
|
result: dict | list[dict]
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
result
|
|
3031
|
+
try:
|
|
3032
|
+
result = _PAYLOAD_PARSERS.get(msg.code, parser_unknown)(msg._pkt.payload, msg)
|
|
3033
|
+
if isinstance(result, dict) and msg.seqn.isnumeric(): # e.g. 22F1/3
|
|
3034
|
+
result["seqx_num"] = msg.seqn
|
|
3035
|
+
except AssertionError as err:
|
|
3036
|
+
_LOGGER.warning(
|
|
3037
|
+
f"{msg!r} < {_INFORM_DEV_MSG} ({err}). "
|
|
3038
|
+
f"This packet could not be parsed completely. "
|
|
3039
|
+
f"Please report this message and any context about what changed on your system when this occurred."
|
|
3040
|
+
)
|
|
3041
|
+
# Return partial result with error info
|
|
3042
|
+
result = {
|
|
3043
|
+
"_payload": msg._pkt.payload,
|
|
3044
|
+
"_parse_error": f"AssertionError: {err}",
|
|
3045
|
+
"_unknown_code": msg.code,
|
|
3046
|
+
}
|
|
3047
|
+
if isinstance(result, dict) and msg.seqn.isnumeric():
|
|
3048
|
+
result["seqx_num"] = msg.seqn
|
|
3004
3049
|
|
|
3005
3050
|
return result
|
ramses_tx/protocol.py
CHANGED
|
@@ -583,7 +583,7 @@ class PortProtocol(_DeviceIdFilterMixin, _BaseProtocol):
|
|
|
583
583
|
self._context.pause_writing()
|
|
584
584
|
|
|
585
585
|
def resume_writing(self) -> None:
|
|
586
|
-
"""Inform the FSM that the Protocol has been
|
|
586
|
+
"""Inform the FSM that the Protocol has been resumed."""
|
|
587
587
|
|
|
588
588
|
super().resume_writing()
|
|
589
589
|
if self._context:
|
|
@@ -597,7 +597,7 @@ class PortProtocol(_DeviceIdFilterMixin, _BaseProtocol):
|
|
|
597
597
|
self._context.pkt_received(pkt)
|
|
598
598
|
|
|
599
599
|
async def _send_impersonation_alert(self, cmd: Command) -> None:
|
|
600
|
-
"""Send
|
|
600
|
+
"""Send a puzzle packet warning that impersonation is occurring."""
|
|
601
601
|
|
|
602
602
|
if _DBG_DISABLE_IMPERSONATION_ALERTS:
|
|
603
603
|
return
|
ramses_tx/ramses.py
CHANGED
|
@@ -377,10 +377,10 @@ CODES_SCHEMA: dict[Code, dict[str, Any]] = { # rf_unknown
|
|
|
377
377
|
I_: r"^(0[0-9A-F][0-9A-F]{8}0[12]){1,4}(0[12]03)?$", # (0[12]03)? only if len(array) == 1
|
|
378
378
|
W_: r"^(0[0-9A-F][0-9A-F]{8}0[12])$", # never an array
|
|
379
379
|
},
|
|
380
|
-
Code._22D0: { # unknown_22d0, HVAC system switch?
|
|
380
|
+
Code._22D0: { # unknown_22d0, Spider thermostat, HVAC system switch?
|
|
381
381
|
SZ_NAME: "message_22d0",
|
|
382
|
-
I_: r"^(00|03)",
|
|
383
|
-
W_: r"^03",
|
|
382
|
+
I_: r"^(00|03)[0-9]{6}$",
|
|
383
|
+
W_: r"^03[0-9]{4}1E14030020$",
|
|
384
384
|
},
|
|
385
385
|
Code._22D9: { # boiler_setpoint
|
|
386
386
|
SZ_NAME: "boiler_setpoint",
|
|
@@ -843,6 +843,7 @@ _DEV_KLASSES_HEAT: dict[str, dict[Code, dict[VerbT, Any]]] = {
|
|
|
843
843
|
Code._000C: {I_: {}},
|
|
844
844
|
Code._000E: {I_: {}},
|
|
845
845
|
Code._0016: {RQ: {}},
|
|
846
|
+
Code._01FF: {I_: {}, RQ: {}},
|
|
846
847
|
Code._042F: {I_: {}},
|
|
847
848
|
Code._1030: {I_: {}},
|
|
848
849
|
Code._1060: {I_: {}},
|
|
@@ -853,9 +854,11 @@ _DEV_KLASSES_HEAT: dict[str, dict[Code, dict[VerbT, Any]]] = {
|
|
|
853
854
|
Code._1F09: {I_: {}},
|
|
854
855
|
Code._1FC9: {I_: {}},
|
|
855
856
|
Code._22C9: {W_: {}}, # DT4R
|
|
857
|
+
Code._22D0: {W_: {}}, # Spider master THM
|
|
856
858
|
Code._2309: {I_: {}, RQ: {}, W_: {}},
|
|
857
859
|
Code._2349: {RQ: {}, W_: {}},
|
|
858
860
|
Code._30C9: {I_: {}},
|
|
861
|
+
Code._3110: {I_: {}}, # Spider THM
|
|
859
862
|
Code._3120: {I_: {}},
|
|
860
863
|
Code._313F: {
|
|
861
864
|
I_: {}
|
|
@@ -880,6 +883,7 @@ _DEV_KLASSES_HEAT: dict[str, dict[Code, dict[VerbT, Any]]] = {
|
|
|
880
883
|
Code._3110: {I_: {}}, # Spider Autotemp
|
|
881
884
|
Code._3150: {I_: {}},
|
|
882
885
|
Code._4E01: {I_: {}}, # Spider Autotemp Zone controller
|
|
886
|
+
Code._4E04: {I_: {}}, # idem
|
|
883
887
|
},
|
|
884
888
|
DevType.TRV: { # e.g. HR92/HR91: Radiator Controller
|
|
885
889
|
Code._0001: {W_: {r"^0[0-9A-F]"}},
|
|
@@ -1178,9 +1182,9 @@ _CODE_ONLY_FROM_CTL: tuple[Code, ...] = tuple(
|
|
|
1178
1182
|
CODES_ONLY_FROM_CTL: tuple[Code, ...] = (
|
|
1179
1183
|
Code._1030,
|
|
1180
1184
|
Code._1F09,
|
|
1181
|
-
Code._22D0,
|
|
1185
|
+
# Code._22D0, # also _W from the Spider master THM! issue #340
|
|
1182
1186
|
Code._313F,
|
|
1183
|
-
) # I packets, TODO: 31Dx too?
|
|
1187
|
+
) # I packets, TODO: 31Dx too? not 31D9/31DA!
|
|
1184
1188
|
|
|
1185
1189
|
#
|
|
1186
1190
|
########################################################################################
|
ramses_tx/transport.py
CHANGED
|
@@ -1598,4 +1598,5 @@ async def transport_factory(
|
|
|
1598
1598
|
|
|
1599
1599
|
# TODO: remove this? better to invoke timeout after factory returns?
|
|
1600
1600
|
await protocol.wait_for_connection_made(timeout=_DEFAULT_TIMEOUT_PORT)
|
|
1601
|
+
# pytest-cov times out in virtual_rf.py when set below 30.0 on GitHub Actions
|
|
1601
1602
|
return transport
|
ramses_tx/version.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|