ramses-rf 0.53.0__py3-none-any.whl → 0.53.1__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/__init__.py CHANGED
@@ -12,7 +12,7 @@ _DBG_FORCE_CLI_DEBUGGING: Final[bool] = (
12
12
  )
13
13
 
14
14
 
15
- if _DBG_FORCE_CLI_DEBUGGING:
15
+ if _DBG_FORCE_CLI_DEBUGGING: # pragma: no cover
16
16
  from .debug import start_debugging
17
17
 
18
18
  start_debugging(True)
ramses_cli/client.py CHANGED
@@ -23,7 +23,7 @@ from ramses_rf.schemas import (
23
23
  SZ_ENABLE_EAVESDROP,
24
24
  SZ_REDUCE_PROCESSING,
25
25
  )
26
- from ramses_tx import is_valid_dev_id, transport_factory
26
+ from ramses_tx import is_valid_dev_id
27
27
  from ramses_tx.logger import CONSOLE_COLS, DEFAULT_DATEFMT, DEFAULT_FMT
28
28
  from ramses_tx.schemas import (
29
29
  SZ_DISABLE_QOS,
@@ -607,15 +607,25 @@ async def async_main(command: str, lib_kwargs: dict[str, Any], **kwargs: Any) ->
607
607
  ]
608
608
  lib_kwargs = lib_kwargs | state["schema"]
609
609
 
610
+ # Explicitly extract input_file if present to ensure it's passed as a named arg
611
+ input_file = lib_kwargs.pop(SZ_INPUT_FILE, None)
612
+
610
613
  # if serial_port == "/dev/ttyMOCK":
611
614
  # from tests.deprecated.mocked_rf import MockGateway # FIXME: for test/dev
612
615
  # gwy = MockGateway(serial_port, **lib_kwargs)
613
616
  # else:
617
+
618
+ # Instantiate Gateway, note: transport_factory is the default, so we don't need to pass it
614
619
  gwy = Gateway(
615
- serial_port, transport_constructor=transport_factory, **lib_kwargs
620
+ serial_port,
621
+ input_file=input_file,
622
+ **lib_kwargs,
616
623
  ) # passes action to gateway
617
624
 
618
- if int(lib_kwargs[SZ_CONFIG][SZ_REDUCE_PROCESSING]) < DONT_CREATE_MESSAGES:
625
+ if (
626
+ int(lib_kwargs.get(SZ_CONFIG, {}).get(SZ_REDUCE_PROCESSING, 0))
627
+ < DONT_CREATE_MESSAGES
628
+ ):
619
629
  # library will not send MSGs to STDOUT, so we'll send PKTs instead
620
630
  colorama_init(autoreset=True) # WIP: remove strip=True
621
631
  gwy.add_msg_handler(handle_msg)
@@ -678,7 +688,7 @@ cli.add_command(execute)
678
688
  cli.add_command(listen)
679
689
 
680
690
 
681
- def main() -> None:
691
+ def main() -> None: # pragma: no cover
682
692
  """Entry point for the CLI.
683
693
 
684
694
  Parses arguments, sets up the event loop (including Windows-specific policies),
@@ -718,5 +728,5 @@ def main() -> None:
718
728
  print(" - finished ramses_rf.\r\n")
719
729
 
720
730
 
721
- if __name__ == "__main__":
731
+ if __name__ == "__main__": # pragma: no cover
722
732
  main()
ramses_cli/discovery.py CHANGED
@@ -17,10 +17,6 @@ from ramses_rf.device import Fakeable
17
17
  from ramses_tx import CODES_SCHEMA, Command, DeviceIdT, Priority
18
18
  from ramses_tx.opentherm import OTB_DATA_IDS
19
19
 
20
- # Beware, none of this is reliable - it is all subject to random change
21
- # However, these serve as examples how to use the other modules
22
-
23
-
24
20
  from ramses_rf.const import ( # noqa: F401, isort: skip, pylint: disable=unused-import
25
21
  I_,
26
22
  RP,
@@ -44,22 +40,19 @@ SCAN_FULL: Final = "scan_full"
44
40
  SCAN_HARD: Final = "scan_hard"
45
41
  SCAN_XXXX: Final = "scan_xxxx"
46
42
 
47
- # DEVICE_ID_REGEX = re.compile(DEVICE_ID_REGEX.ANY)
48
-
49
-
50
43
  _LOGGER = logging.getLogger(__name__)
51
44
 
52
45
 
53
46
  def script_decorator(fnc: Callable[..., Any]) -> Callable[..., Any]:
54
47
  @functools.wraps(fnc)
55
- def wrapper(gwy: Gateway, *args: Any, **kwargs: Any) -> None:
48
+ async def wrapper(gwy: Gateway, *args: Any, **kwargs: Any) -> None:
56
49
  gwy.send_cmd(
57
50
  Command._puzzle(message="Script begins:"),
58
51
  priority=Priority.HIGHEST,
59
52
  num_repeats=3,
60
53
  )
61
54
 
62
- fnc(gwy, *args, **kwargs)
55
+ await fnc(gwy, *args, **kwargs)
63
56
 
64
57
  gwy.send_cmd(
65
58
  Command._puzzle(message="Script done."),
@@ -67,8 +60,6 @@ def script_decorator(fnc: Callable[..., Any]) -> Callable[..., Any]:
67
60
  num_repeats=3,
68
61
  )
69
62
 
70
- return None
71
-
72
63
  return wrapper
73
64
 
74
65
 
@@ -93,7 +84,12 @@ def spawn_scripts(gwy: Gateway, **kwargs: Any) -> list[asyncio.Task[None]]:
93
84
  _LOGGER.warning(f"Script: {kwargs[EXEC_SCR][0]}() - unknown script")
94
85
  else:
95
86
  _LOGGER.info(f"Script: {kwargs[EXEC_SCR][0]}().- starts...")
96
- tasks += [asyncio.create_task(script(gwy, kwargs[EXEC_SCR][1]))]
87
+ # script_poll_device returns a list of tasks, others return a coroutine
88
+ result = script(gwy, kwargs[EXEC_SCR][1])
89
+ if isinstance(result, list):
90
+ tasks.extend(result)
91
+ else:
92
+ tasks.append(asyncio.create_task(result))
97
93
 
98
94
  gwy._tasks.extend(tasks)
99
95
  return tasks
@@ -104,14 +100,6 @@ async def exec_cmd(gwy: Gateway, **kwargs: Any) -> None:
104
100
  await gwy.async_send_cmd(cmd, priority=Priority.HIGH, wait_for_reply=True)
105
101
 
106
102
 
107
- # @script_decorator
108
- # async def script_scan_001(gwy: Gateway, dev_id: DeviceIdT):
109
- # _LOGGER.warning("scan_001() invoked - expect a lot of nonsense")
110
- # for idx in range(0x10):
111
- # gwy.send_cmd(Command.from_attrs(W_, dev_id, Code._000E, f"{idx:02X}0050"))
112
- # gwy.send_cmd(Command.from_attrs(RQ, dev_id, Code._000E, f"{idx:02X}00C8"))
113
-
114
-
115
103
  async def get_faults(
116
104
  gwy: Gateway, ctl_id: DeviceIdT, start: int = 0, limit: int = 0x3F
117
105
  ) -> None:
@@ -199,7 +187,7 @@ def script_poll_device(gwy: Gateway, dev_id: DeviceIdT) -> list[asyncio.Task[Non
199
187
  async def script_scan_disc(gwy: Gateway, dev_id: DeviceIdT) -> None:
200
188
  _LOGGER.warning("scan_disc() invoked...")
201
189
 
202
- await gwy.get_device(dev_id).discover() # discover_flag=Discover.DEFAULT)
190
+ await gwy.get_device(dev_id).discover()
203
191
 
204
192
 
205
193
  @script_decorator
@@ -340,9 +328,7 @@ async def script_scan_otb_hard(gwy: Gateway, dev_id: DeviceIdT) -> None:
340
328
 
341
329
 
342
330
  @script_decorator
343
- async def script_scan_otb_map(
344
- gwy: Gateway, dev_id: DeviceIdT
345
- ) -> None: # Tested only upon a R8820A
331
+ async def script_scan_otb_map(gwy: Gateway, dev_id: DeviceIdT) -> None:
346
332
  _LOGGER.warning("script_scan_otb_map invoked - expect a lot of nonsense")
347
333
 
348
334
  RAMSES_TO_OPENTHERM = {
@@ -364,9 +350,7 @@ async def script_scan_otb_map(
364
350
 
365
351
 
366
352
  @script_decorator
367
- async def script_scan_otb_ramses(
368
- gwy: Gateway, dev_id: DeviceIdT
369
- ) -> None: # Tested only upon a R8820A
353
+ async def script_scan_otb_ramses(gwy: Gateway, dev_id: DeviceIdT) -> None:
370
354
  _LOGGER.warning("script_scan_otb_ramses invoked - expect a lot of nonsense")
371
355
 
372
356
  _CODES = (
@@ -394,7 +378,7 @@ async def script_scan_otb_ramses(
394
378
  Code._3223,
395
379
  Code._3EF0, # rel. modulation level / RelativeModulationLevel (also, below)
396
380
  Code._3EF1, # rel. modulation level / RelativeModulationLevel
397
- ) # excl. 3150, 3220
381
+ )
398
382
 
399
383
  for c in _CODES:
400
384
  gwy.send_cmd(Command.from_attrs(RQ, dev_id, c, "00"), priority=Priority.LOW)
ramses_rf/database.py CHANGED
@@ -272,18 +272,21 @@ class MessageIndex:
272
272
 
273
273
  return old
274
274
 
275
- def add_record(self, src: str, code: str = "", verb: str = "") -> None:
275
+ def add_record(
276
+ self, src: str, code: str = "", verb: str = "", payload: str = "00"
277
+ ) -> None:
276
278
  """
277
279
  Add a single record to the MessageIndex with timestamp `now()` and no Message contents.
278
280
 
279
281
  :param src: device id to use as source address
280
282
  :param code: device id to use as destination address (can be identical)
281
283
  :param verb: two letter verb str to use
284
+ :param payload: payload str to use
282
285
  """
283
- # Used by OtbGateway init, via entity_base.py
286
+ # Used by OtbGateway init, via entity_base.py (code=_3220)
284
287
  _now: dt = dt.now()
285
288
  dtm: DtmStrT = _now.isoformat(timespec="microseconds") # type: ignore[assignment]
286
- hdr = f"{code}|{verb}|{src}|00" # dummy record has no contents
289
+ hdr = f"{code}|{verb}|{src}|{payload}"
287
290
 
288
291
  dup = self._delete_from(hdr=hdr)
289
292
 
ramses_rf/device/heat.py CHANGED
@@ -668,11 +668,13 @@ class OtbGateway(Actuator, HeatDemand): # OTB (10): 3220 (22D9, others)
668
668
  self._child_id = FC # NOTE: domain_id
669
669
 
670
670
  # TODO(eb): cleanup
671
- if self._gwy.msg_db:
672
- self._add_record(id=self.id, code=Code._3220, verb="RP")
673
- # adds a "sim" RP opentherm_msg to the SQLite MessageIndex with code _3220
674
- # causes exc when fetching ALL, when no "real" msg was added to _msgs_. We skip those.
675
- else:
671
+ if not self._gwy.msg_db:
672
+ # self._add_record(
673
+ # id=self.id, code=Code._3220, verb="RP", payload="00C0060101"
674
+ # ) # is parsed but pollutes the client.py
675
+ # adds a "sim" RP opentherm_msg to the SQLite MessageIndex with code _3220
676
+ # causes exc when fetching ALL, when no "real" msg was added to _msgs_. We skip those.
677
+ # else:
676
678
  self._msgz[Code._3220] = {RP: {}} # No ctx! (not None)
677
679
 
678
680
  # lf._use_ot = self._gwy.config.use_native_ot
ramses_rf/entity_base.py CHANGED
@@ -324,12 +324,16 @@ class _MessageDB(_Entity):
324
324
  ]
325
325
 
326
326
  def _add_record(
327
- self, id: DeviceIdT, code: Code | None = None, verb: str = " I"
327
+ self,
328
+ id: DeviceIdT,
329
+ code: Code | None = None,
330
+ verb: str = " I",
331
+ payload: str = "00",
328
332
  ) -> None:
329
333
  """Add a (dummy) record to the central SQLite MessageIndex."""
330
- # used by heat.py init
334
+ # used by heat.py.OtbGateway init
331
335
  if self._gwy.msg_db:
332
- self._gwy.msg_db.add_record(id, code=str(code), verb=verb)
336
+ self._gwy.msg_db.add_record(id, code=str(code), verb=verb, payload=payload)
333
337
  # else:
334
338
  # _LOGGER.warning("Missing MessageIndex")
335
339
  # raise NotImplementedError
ramses_rf/gateway.py CHANGED
@@ -107,7 +107,7 @@ class Gateway(Engine):
107
107
 
108
108
  :param port_name: The serial port name (e.g., '/dev/ttyUSB0') or None if using a file.
109
109
  :type port_name: str | None
110
- :param input_file: Path to a packet log file for playback, defaults to None.
110
+ :param input_file: Path to a packet log file for playback/parsing, defaults to None.
111
111
  :type input_file: str | None, optional
112
112
  :param port_config: Configuration dictionary for the serial port, defaults to None.
113
113
  :type port_config: PortConfigT | None, optional
@@ -224,9 +224,10 @@ class Gateway(Engine):
224
224
  )
225
225
 
226
226
  # initialize SQLite index, set in _tx/Engine
227
- if self._sqlite_index: # TODO(eb): default to ON in Q4 2025
227
+ if self._sqlite_index: # TODO(eb): default to True in Q1 2026
228
228
  _LOGGER.info("Ramses RF starts SQLite MessageIndex")
229
- self.create_sqlite_message_index() # if activated in ramses_cc > Engine
229
+ # if activated in ramses_cc > Engine or set in tests
230
+ self.create_sqlite_message_index()
230
231
 
231
232
  # temporarily turn on discovery, remember original state
232
233
  self.config.disable_discovery, disable_discovery = (
ramses_rf/system/zones.py CHANGED
@@ -689,7 +689,7 @@ class Zone(ZoneSchedule):
689
689
 
690
690
  @property
691
691
  def heating_type(self) -> str | None:
692
- """Return the type of the zone/DHW (e.g. electric_zone, stored_dhw)."""
692
+ """Get the type of the zone/DHW (e.g. electric_zone, stored_dhw)."""
693
693
 
694
694
  if self._SLUG is None: # isinstance(self, ???)
695
695
  return None
@@ -697,12 +697,13 @@ class Zone(ZoneSchedule):
697
697
 
698
698
  @property
699
699
  def name(self) -> str | None: # 0004
700
- """Return the name of the zone."""
700
+ """Get the name of the zone."""
701
701
 
702
702
  if self._gwy.msg_db:
703
703
  msgs = self._gwy.msg_db.get(
704
704
  code=Code._0004, src=self._z_id, ctx=self._z_idx
705
705
  )
706
+ _LOGGER.debug(f"Pick Zone.name from: {msgs}[0])") # DEBUG issue #317
706
707
  return msgs[0].payload.get(SZ_NAME) if msgs else None
707
708
 
708
709
  return self._msg_value(Code._0004, key=SZ_NAME) # type: ignore[no-any-return]
ramses_rf/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """RAMSES RF - a RAMSES-II protocol decoder & analyser (application layer)."""
2
2
 
3
- __version__ = "0.53.0"
3
+ __version__ = "0.53.1"
4
4
  VERSION = __version__
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ramses_rf
3
- Version: 0.53.0
3
+ Version: 0.53.1
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
@@ -1,56 +1,56 @@
1
- ramses_cli/__init__.py,sha256=uvGzWqOf4avvgzxJNSLFWEelIWqSZ-AeLAZzg5x58bc,397
2
- ramses_cli/client.py,sha256=OpsVfrdwChrBKia_yfAlI60ERMg7KAk3BWRpQyY3AWA,24762
1
+ ramses_cli/__init__.py,sha256=d_3uIFkK8JnWOxknrBloKCe6-vI9Ouo_KGqR4kfBQW8,417
2
+ ramses_cli/client.py,sha256=Fd6hXGBvI-i89Xu8QbBU9NW-dWlQjSer-_LYAtvJPW4,25055
3
3
  ramses_cli/debug.py,sha256=PLcz-3PjUiMVqtD_p6VqTA92eHUM58lOBFXh_qgQ_wA,576
4
- ramses_cli/discovery.py,sha256=MWoahBnAAVzfK2S7EDLsY2WYqN_ZK9L-lktrj8_4cb0,12978
4
+ ramses_cli/discovery.py,sha256=WTcoFH5hNhQ1AeOZtpdZIVYwdUfmUKlq2iBpa-KcgoI,12512
5
5
  ramses_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  ramses_cli/utils/cat_slow.py,sha256=AhUpM5gnegCitNKU-JGHn-DrRzSi-49ZR1Qw6lxe_t8,607
7
7
  ramses_cli/utils/convert.py,sha256=N3LxGe3_0pclijtmYW-ChqCuPTzbkoJA4XNAnoSnBk0,1806
8
8
  ramses_rf/__init__.py,sha256=AXsCK1Eh9FWeAI9D_zY_2KB0dqrTb9a5TNY1NvyQaDM,1271
9
9
  ramses_rf/binding_fsm.py,sha256=fuqvcc9YW-wr8SPH8zadpPqrHAvzl_eeWF-IBtlLppY,26632
10
10
  ramses_rf/const.py,sha256=L3z31CZ-xqno6oZp_h-67CB_5tDDqTwSWXsqRtsjMcs,5460
11
- ramses_rf/database.py,sha256=9nY1Wt2hjkBoDvDqopzPP3mPSH5-Q5XHPQuJnM2GHOw,21362
11
+ ramses_rf/database.py,sha256=AHXIx5pO0qgiHbBQxLBegnk0e1M4SFwD9Spd4eyW_eQ,21428
12
12
  ramses_rf/dispatcher.py,sha256=YjEU-QrBLo9IfoEhJo2ikg_FxOaMYoWvzelr9Vi-JZ8,11398
13
- ramses_rf/entity_base.py,sha256=6jsHQD_5buaV56rF6nTgzPKOLsXFGVly9EHAeigqeRg,59039
13
+ ramses_rf/entity_base.py,sha256=L47P_6CRz3tLDzOzII9AgmueKDb-Bp7Ot3vVsr8jo10,59121
14
14
  ramses_rf/exceptions.py,sha256=mt_T7irqHSDKir6KLaf6oDglUIdrw0S40JbOrWJk5jc,3657
15
- ramses_rf/gateway.py,sha256=KWE7ZkHGPcZFp9eaKN9zXchkykMJLEx_k5Le99or5lQ,30064
15
+ ramses_rf/gateway.py,sha256=hHsXbpeZ2Fetpu3jzv8UeUwlNf4rsxOD7eOHuLY83Xk,30101
16
16
  ramses_rf/helpers.py,sha256=TNk_QkpIOB3alOp1sqnA9LOzi4fuDCeapNlW3zTzNas,4250
17
17
  ramses_rf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  ramses_rf/schemas.py,sha256=X1GAK3kttuLMiSCUDY2s-85fgBxPeU8xiDa6gJ1I5mY,13543
19
- ramses_rf/version.py,sha256=FlL_C6hC_miMOJxgK12zDE2CvaAfRA3KoLOL1_U-7QE,125
19
+ ramses_rf/version.py,sha256=yMI3sQ8wyLuGx_Hm7ZYNuwbqFZ3Qhr3GJXdkdsGwh7s,125
20
20
  ramses_rf/device/__init__.py,sha256=sUbH5dhbYFXSoM_TPFRutpRutBRpup7_cQ9smPtDTy8,4858
21
21
  ramses_rf/device/base.py,sha256=Tu5I8Lj7KplfRsIBQAYjilS6YPgTyjpU8qgKugMR2Jk,18281
22
- ramses_rf/device/heat.py,sha256=T-ENFzH1AEOd0m4-TgV-Qk0TnLNir_pYmyKlJtxy6NI,54538
22
+ ramses_rf/device/heat.py,sha256=CU6GlIgjuYD21braJ_RJlS56zP47TGXNxXnZeavfEMY,54654
23
23
  ramses_rf/device/hvac.py,sha256=vdgiPiLtCAGr7CVsGhQl6XuAFkyYdQSE_2AEdCmRl2I,48502
24
24
  ramses_rf/system/__init__.py,sha256=uZLKio3gLlBzePa2aDQ1nxkcp1YXOGrn6iHTG8LiNIw,711
25
25
  ramses_rf/system/faultlog.py,sha256=GdGmVGT3137KsTlV_nhccgIFEmYu6DFsLTn4S-8JSok,12799
26
26
  ramses_rf/system/heat.py,sha256=qQmzgmyHy2x87gHAstn0ee7ZVVOq-GJIfDxCrC-6gFU,39254
27
27
  ramses_rf/system/schedule.py,sha256=Ts6tdZPTQLV5NkgwA73tPa5QUsnZNIIuYoKC-8VsXDk,18808
28
- ramses_rf/system/zones.py,sha256=qMv7CuvZUKBNLpPRXgFA70NFRStgVJcw062h5mc3Y8Q,37034
28
+ ramses_rf/system/zones.py,sha256=6VbPsOuNbGwBUuiRu8w9D1Q18SHKkuZa2YtKTE5nqlo,37110
29
29
  ramses_tx/__init__.py,sha256=sqnjM7pUGJDmec6igTtKViSB8FLX49B5gwhAmcY9ERY,3596
30
30
  ramses_tx/address.py,sha256=IuwUwZxykn3fP1UCRcv4D-zbTICBe2FJjDAFX5X6VoI,9108
31
31
  ramses_tx/command.py,sha256=drxmpdM4YgyPg4h0QIr1ouxK9QjfeLVgnFpDRox0CCY,125652
32
- ramses_tx/const.py,sha256=gmRQ59V5AJx2TlACL3tDSBy5VpvEOVbQfG0z7WjFCKE,32941
32
+ ramses_tx/const.py,sha256=jiE2UaGBJ5agr68EMrcEHWtVz2KMidU7c7rRYCIiaoM,33010
33
33
  ramses_tx/exceptions.py,sha256=FJSU9YkvpKjs3yeTqUJX1o3TPFSe_B01gRGIh9b3PNc,2632
34
34
  ramses_tx/fingerprints.py,sha256=nfftA1E62HQnb-eLt2EqjEi_la0DAoT0wt-PtTMie0s,11974
35
35
  ramses_tx/frame.py,sha256=GzNsXr15YLeidJYGtk_xPqsZQh4ehDDlUCtT6rTDhT8,22046
36
- ramses_tx/gateway.py,sha256=Fl6EqAUU2DnLOiA2_87sS7VPBEyxA1a_ICCmg55WMMA,11494
37
- ramses_tx/helpers.py,sha256=VIPSw0lrDrE3S92YchzeRo7G2AlQ_vE8OuYUF-rlpQw,33616
36
+ ramses_tx/gateway.py,sha256=H4JtIkp7JFMyZZ76sij67rTbjc1i3iWaIf6tuxIpHAg,11529
37
+ ramses_tx/helpers.py,sha256=96OvSOWYuMcr89_c-3dRnqHZaMOctCO94uo1hETh3bc,33613
38
38
  ramses_tx/logger.py,sha256=1iKRHKUaqHqGd76CkE_6mCVR0sYODtxshRRwfY61fTk,10426
39
- ramses_tx/message.py,sha256=HUSNiBgGlnWEBeBqb4K28GcU5oB7RbS9a0JC2p9SGe4,13676
39
+ ramses_tx/message.py,sha256=zsyDQztSUYeqj3-P598LSmy9ODQY2BUCzWxSoZds6bM,13953
40
40
  ramses_tx/opentherm.py,sha256=58PXz9l5x8Ou6Fm3y-R_UnGHCYahoi2RKIDdYStUMzk,42378
41
41
  ramses_tx/packet.py,sha256=_nzuInS_WhdOI26SYvgsdDqIaDvVNguc2YDwdPOVCbU,7661
42
- ramses_tx/parsers.py,sha256=qQz8VOKaH26MUvo9AfUwhrnbH7VwXLxFc2gKJYvjXvY,148571
42
+ ramses_tx/parsers.py,sha256=vHaVdapzW9TtQhCjR9XuaLLkp5ZMXKmTm05zbu0ekW0,148576
43
43
  ramses_tx/protocol.py,sha256=nBPKCD1tcGp_FiX0qhsY0XoGO_h87w5cYywBjSpum4w,33048
44
44
  ramses_tx/protocol_fsm.py,sha256=o9vLvlXor3LkPgsY1zii5P1R01GzYLf_PECDdoxtC24,27520
45
45
  ramses_tx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  ramses_tx/ramses.py,sha256=vp748Tf_a-56OMM8CWDA2ZktRfTuj0QVyPRcnsOstSM,53983
47
47
  ramses_tx/schemas.py,sha256=Hrmf_q9bAZtkKJzGu6GtUO0QV_-K9i4L99EzGWR13eE,13408
48
- ramses_tx/transport.py,sha256=Tk2CPAu9W0T4HCe6Q2VUYex2bFNZlyYsluP7iLf0rdQ,76277
48
+ ramses_tx/transport.py,sha256=hktWSDOzKUME967w58JB4z3MgmCMiQ0QZI0x4-Awaas,76419
49
49
  ramses_tx/typed_dicts.py,sha256=w-0V5t2Q3GiNUOrRAWiW9GtSwbta_7luME6GfIb1zhI,10869
50
50
  ramses_tx/typing.py,sha256=eF2SlPWhNhEFQj6WX2AhTXiyRQVXYnFutiepllYl2rI,5042
51
- ramses_tx/version.py,sha256=DToXw6Td-r5_4JxS4fbBBGkajaXoEj8xCJOh7vaNcQI,123
52
- ramses_rf-0.53.0.dist-info/METADATA,sha256=YkSNqr4RWgOsSi-z8SO5H4LEbzOI87IVYurjHWpUgRc,4179
53
- ramses_rf-0.53.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
54
- ramses_rf-0.53.0.dist-info/entry_points.txt,sha256=NnyK29baOCNg8DinPYiZ368h7MTH7bgTW26z2A1NeIE,50
55
- ramses_rf-0.53.0.dist-info/licenses/LICENSE,sha256=ptVutrtSMr7X-ek6LduiD8Cce4JsNn_8sR8MYlm-fvo,1086
56
- ramses_rf-0.53.0.dist-info/RECORD,,
51
+ ramses_tx/version.py,sha256=g3EPkHx--jzBHNq9oau7n6DOcVjdbcDWz5m13ZpzvEQ,123
52
+ ramses_rf-0.53.1.dist-info/METADATA,sha256=OC2EXqy23YPE-jCqrXSdLM4cN5_7a9M3fBWh15r3kqg,4179
53
+ ramses_rf-0.53.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
54
+ ramses_rf-0.53.1.dist-info/entry_points.txt,sha256=NnyK29baOCNg8DinPYiZ368h7MTH7bgTW26z2A1NeIE,50
55
+ ramses_rf-0.53.1.dist-info/licenses/LICENSE,sha256=ptVutrtSMr7X-ek6LduiD8Cce4JsNn_8sR8MYlm-fvo,1086
56
+ ramses_rf-0.53.1.dist-info/RECORD,,
ramses_tx/const.py CHANGED
@@ -276,7 +276,9 @@ class AttrDict(dict): # type: ignore[type-arg]
276
276
  return self._forward[name[1:]]
277
277
  elif name.isupper() and name.lower() in self._reverse: # map.DHW_SENSOR -> "0D"
278
278
  return self[name.lower()]
279
- return self.__getattribute__(name)
279
+ raise AttributeError(
280
+ f"'{type(self).__name__}' object has no attribute '{name}'"
281
+ )
280
282
 
281
283
  def _hex(self, key: str) -> str:
282
284
  """Return the key/ID (2-byte hex string) of the two-way dict (e.g. '04').
ramses_tx/gateway.py CHANGED
@@ -114,7 +114,9 @@ class Engine:
114
114
  self._include,
115
115
  self._exclude,
116
116
  )
117
- self._sqlite_index = kwargs.pop(SZ_SQLITE_INDEX, False) # default True?
117
+ self._sqlite_index = kwargs.pop(
118
+ SZ_SQLITE_INDEX, False
119
+ ) # TODO Q1 2026: default True
118
120
  self._log_all_mqtt = kwargs.pop(SZ_LOG_ALL_MQTT, False)
119
121
  self._kwargs: dict[str, Any] = kwargs # HACK
120
122
 
ramses_tx/helpers.py CHANGED
@@ -149,7 +149,7 @@ def timestamp() -> float:
149
149
 
150
150
 
151
151
  def dt_now() -> dt:
152
- """Return the current datetime as a local/naive datetime object.
152
+ """Get the current datetime as a local/naive datetime object.
153
153
 
154
154
  This is slower, but potentially more accurate, than dt.now(), and is used mainly for
155
155
  packet timestamps.
ramses_tx/message.py CHANGED
@@ -70,22 +70,24 @@ class MessageBase:
70
70
  self.verb: VerbT = pkt.verb
71
71
  self.seqn: str = (
72
72
  pkt.seqn
73
- ) # the msg is part of a set for 1 Code, received in order
73
+ ) # the msg is part of a set for a Code, received in order
74
74
  self.code: Code = pkt.code
75
75
  self.len: int = pkt._len
76
76
 
77
- self._payload = self._validate(
78
- self._pkt.payload
79
- ) # ? may raise InvalidPacketError
77
+ self._payload = self._validate(self._pkt.payload) # ? may raise PacketInvalid
80
78
 
81
79
  self._str: str = None # type: ignore[assignment]
82
80
 
83
81
  def __repr__(self) -> str:
84
- """Return an unambiguous string representation of this object."""
82
+ """
83
+ :return: an unambiguous string representation of this object.
84
+ """
85
85
  return str(self._pkt) # repr or str?
86
86
 
87
87
  def __str__(self) -> str:
88
- """Return a brief readable string representation of this object."""
88
+ """
89
+ :return: a brief readable string representation of this object.
90
+ """
89
91
 
90
92
  def ctx(pkt: Packet) -> str:
91
93
  ctx = {True: "[..]", False: "", None: "??"}.get(pkt._ctx, pkt._ctx) # type: ignore[arg-type]
@@ -128,17 +130,22 @@ class MessageBase:
128
130
  return self.dtm < other.dtm
129
131
 
130
132
  def _name(self, addr: Address) -> str:
131
- """Return a friendly name for an Address, or a Device."""
133
+ """
134
+ :return: a friendly name for an Address, or a Device.
135
+ """
132
136
  return f" {addr.id}" # can't do 'CTL:123456' instead of ' 01:123456'
133
137
 
134
138
  @property
135
139
  def payload(self): # type: ignore[no-untyped-def] # FIXME -> dict | list:
136
- """Return the payload."""
140
+ """
141
+ :return: the payload.
142
+ """
137
143
  return self._payload
138
144
 
139
145
  @property
140
146
  def _has_payload(self) -> bool:
141
- """Return False if there is no payload (may falsely Return True).
147
+ """
148
+ :return: False if there is no payload (may falsely return True).
142
149
 
143
150
  The message (i.e. the raw payload) may still have an idx.
144
151
  """
@@ -234,7 +241,7 @@ class MessageBase:
234
241
  return {}
235
242
 
236
243
  # TODO: also 000C (but is a complex idx)
237
- # TODO: also 3150 (when not domain, and will be array if so)
244
+ # TODO: also 3150 (when not domain; will be array in that case)
238
245
  if self.code in (Code._000A, Code._2309) and self.src.type == DEV_TYPE_MAP.UFC:
239
246
  assert isinstance(self._pkt._idx, str) # mypy hint
240
247
  return {IDX_NAMES[Code._22C9]: self._pkt._idx}
@@ -250,7 +257,7 @@ class MessageBase:
250
257
  """Validate a message packet payload, and parse it if valid.
251
258
 
252
259
  :return: a dict containing key: value pairs, or a list of those created from the payload
253
- :raises an InvalidPacketError exception if it is not valid.
260
+ :raises PacketInvalid exception if it is not valid.
254
261
  """
255
262
 
256
263
  try: # parse the payload
@@ -292,7 +299,7 @@ class MessageBase:
292
299
 
293
300
 
294
301
  class Message(MessageBase):
295
- """Extend the Message class, so is useful to a stateful Gateway.
302
+ """Extend the Message class, so it is useful to a stateful Gateway.
296
303
 
297
304
  Adds _expired attr to the Message class.
298
305
  """
@@ -303,7 +310,7 @@ class Message(MessageBase):
303
310
  # .HAS_DIED = 1.0 # fraction_expired >= 1.0 (is expected lifespan)
304
311
  IS_EXPIRING = 0.8 # fraction_expired >= 0.8 (and < HAS_EXPIRED)
305
312
 
306
- _gwy: Gateway
313
+ _gwy: Gateway | None = None
307
314
  _fraction_expired: float | None = None
308
315
 
309
316
  @classmethod
@@ -318,13 +325,19 @@ class Message(MessageBase):
318
325
 
319
326
  @property
320
327
  def _expired(self) -> bool:
321
- """Return True if the message is dated (or False otherwise)."""
328
+ """
329
+ :return: True if the message is dated, False otherwise
330
+ """
322
331
  # fraction_expired = (dt_now - self.dtm - _TD_SECONDS_003) / self._pkt._lifespan
323
332
  # TODO: keep none >7d, even 10E0, etc.
324
333
 
325
334
  def fraction_expired(lifespan: td) -> float:
326
- """Return the packet's age as fraction of its 'normal' life span."""
327
- return (self._gwy._dt_now() - self.dtm - _TD_SECS_003) / lifespan
335
+ """
336
+ :return: the packet's age as fraction of its 'normal' life span.
337
+ """
338
+ if self._gwy: # self._gwy is set in ramses_tx.gateway.Engine._msg_handler
339
+ return (self._gwy._dt_now() - self.dtm - _TD_SECS_003) / lifespan
340
+ return (dt.now() - self.dtm - _TD_SECS_003) / lifespan
328
341
 
329
342
  # 1. Look for easy win...
330
343
  if self._fraction_expired is not None:
ramses_tx/parsers.py CHANGED
@@ -3291,13 +3291,13 @@ def parser_3220(payload: str, msg: Message) -> dict[str, Any]:
3291
3291
  "FFFF",
3292
3292
  ), f"OpenTherm: Invalid msg-type|data-value: {ot_type}|{payload[6:10]}"
3293
3293
 
3294
- # HACK: These OT data id can pop in/out of 47AB, which is an invalid value
3294
+ # HACK: These OT data id's can pop in/out of 47AB, which is an invalid value
3295
3295
  if payload[6:] == "47AB" and ot_id in (0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C):
3296
3296
  ot_value[SZ_VALUE] = None
3297
3297
  # HACK: This OT data id can be 1980, which is an invalid value
3298
3298
  if payload[6:] == "1980" and ot_id: # CH pressure is 25.5 bar!
3299
3299
  ot_value[SZ_VALUE] = None
3300
- # HACK: Done above, not in OT.decode_frame() as they isn't in the OT specification
3300
+ # HACK: Done above, not in OT.decode_frame() as values aren't in the OT specification
3301
3301
 
3302
3302
  if ot_type not in _LIST:
3303
3303
  assert ot_type in (
ramses_tx/transport.py CHANGED
@@ -772,7 +772,7 @@ class _ReadTransport(_BaseTransport):
772
772
  if self._closing is True: # raise, or warn & return?
773
773
  raise exc.TransportError("Transport is closing or has closed")
774
774
 
775
- # TODO: can we switch to call_soon now QoS has been refactored?
775
+ # TODO: can we switch to call_soon now that QoS has been refactored?
776
776
  # NOTE: No need to use call_soon() here, and they may break Qos/Callbacks
777
777
  # NOTE: Thus, excepts need checking
778
778
  try: # below could be a call_soon?
@@ -783,7 +783,7 @@ class _ReadTransport(_BaseTransport):
783
783
  _LOGGER.error("%s < exception from msg layer: %s", pkt, err)
784
784
 
785
785
  async def write_frame(self, frame: str, disable_tx_limits: bool = False) -> None:
786
- """ "Transmit a frame via the underlying handler (e.g. serial port, MQTT).
786
+ """Transmit a frame via the underlying handler (e.g. serial port, MQTT).
787
787
 
788
788
  :param frame: The frame to write.
789
789
  :type frame: str
@@ -811,7 +811,7 @@ class _FullTransport(_ReadTransport): # asyncio.Transport
811
811
  self._transmit_times: deque[dt] = deque(maxlen=_MAX_TRACKED_TRANSMITS)
812
812
 
813
813
  def _dt_now(self) -> dt:
814
- """Return a precise datetime, using the current dtm.
814
+ """Get a precise datetime, using the current dtm.
815
815
 
816
816
  :return: Current datetime.
817
817
  :rtype: dt
@@ -1945,7 +1945,7 @@ async def transport_factory(
1945
1945
  :type port_name: SerPortNameT | None, optional
1946
1946
  :param port_config: Configuration dictionary for serial port, defaults to None.
1947
1947
  :type port_config: PortConfigT | None, optional
1948
- :param packet_log: Path to a file containing packet logs for playback, defaults to None.
1948
+ :param packet_log: Path to a file containing packet logs for playback/parsing, defaults to None.
1949
1949
  :type packet_log: str | None, optional
1950
1950
  :param packet_dict: Dictionary of packets for playback, defaults to None.
1951
1951
  :type packet_dict: dict[str, str] | None, optional
@@ -2024,6 +2024,9 @@ async def transport_factory(
2024
2024
  )
2025
2025
 
2026
2026
  if len([x for x in (packet_dict, packet_log, port_name) if x is not None]) != 1:
2027
+ _LOGGER.warning(
2028
+ f"Input: packet_dict: {packet_dict}, packet_log: {packet_log}, port_name: {port_name}"
2029
+ )
2027
2030
  raise exc.TransportSourceInvalid(
2028
2031
  "Packet source must be exactly one of: packet_dict, packet_log, port_name"
2029
2032
  )
ramses_tx/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """RAMSES RF - a RAMSES-II protocol decoder & analyser (transport layer)."""
2
2
 
3
- __version__ = "0.53.0"
3
+ __version__ = "0.53.1"
4
4
  VERSION = __version__