ramses-rf 0.52.2__py3-none-any.whl → 0.52.3__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/binding_fsm.py CHANGED
@@ -178,6 +178,8 @@ class BindContextBase:
178
178
  self, state: type[BindStateBase], result: asyncio.Future[Message] | None = None
179
179
  ) -> None:
180
180
  """Transition the State of the Context, and process the result, if any."""
181
+ # Ensure prev_state is always available, not only during debugging
182
+ prev_state = self._state
181
183
 
182
184
  # if False and result:
183
185
  # try:
@@ -185,10 +187,6 @@ class BindContextBase:
185
187
  # except exc.BindingError as err:
186
188
  # self._fut.set_result(err)
187
189
 
188
- if _DBG_MAINTAIN_STATE_CHAIN: # HACK for debugging
189
- # if prev_state in (None, )
190
- prev_state = self._state
191
-
192
190
  self._state = state(self)
193
191
  if not self.is_binding:
194
192
  self._is_respondent = None
@@ -197,6 +195,14 @@ class BindContextBase:
197
195
  elif state is SuppSendOfferWaitForAccept:
198
196
  self._is_respondent = False
199
197
 
198
+ # Log binding completion transitions
199
+ if isinstance(
200
+ self._state, (RespHasBoundAsRespondent, SuppHasBoundAsSupplicant)
201
+ ):
202
+ _LOGGER.info(
203
+ f"{self._dev.id}: Binding process completed: {type(prev_state).__name__} -> {state.__name__} (role: {self.role})"
204
+ )
205
+
200
206
  if _DBG_MAINTAIN_STATE_CHAIN: # HACK for debugging
201
207
  setattr(self._state, "_prev_state", prev_state) # noqa: B010
202
208
 
@@ -663,6 +669,10 @@ class RespHasBoundAsRespondent(BindStateBase):
663
669
 
664
670
  _attr_role = BindRole.IS_DORMANT
665
671
 
672
+ def __init__(self, context: BindContextBase) -> None:
673
+ super().__init__(context)
674
+ _LOGGER.info(f"{context._dev.id}: Binding completed as respondent")
675
+
666
676
 
667
677
  class RespIsWaitingForAddenda(_DevIsWaitingForMsg, BindStateBase):
668
678
  """Respondent has received a Confirm & is waiting for an Addenda."""
@@ -715,6 +725,10 @@ class SuppHasBoundAsSupplicant(BindStateBase):
715
725
 
716
726
  _attr_role = BindRole.IS_DORMANT
717
727
 
728
+ def __init__(self, context: BindContextBase) -> None:
729
+ super().__init__(context)
730
+ _LOGGER.info(f"{context._dev.id}: Binding completed as supplicant")
731
+
718
732
 
719
733
  class SuppIsReadyToSendAddenda(
720
734
  _DevIsReadyToSendCmd, BindStateBase
ramses_rf/database.py CHANGED
@@ -334,7 +334,7 @@ class MessageIndex:
334
334
  payload_keys(msg.payload),
335
335
  ),
336
336
  )
337
- _LOGGER.debug(f"Added {msg} to gwy.msg_db")
337
+ # _LOGGER.debug(f"Added {msg} to gwy.msg_db")
338
338
 
339
339
  return _old_msgs[0] if _old_msgs else None
340
340
 
@@ -435,7 +435,7 @@ class MessageIndex:
435
435
 
436
436
  def qry_dtms(self, **kwargs: bool | dt | str) -> list[Any]:
437
437
  """
438
- Select from the ImageIndex a list of dtms that match the provided arguments.
438
+ Select from the MessageIndex a list of dtms that match the provided arguments.
439
439
 
440
440
  :param kwargs: data table field names and criteria
441
441
  :return: list of unformatted dtms that match, useful for msg lookup, or an empty list if 0 matches
ramses_rf/device/heat.py CHANGED
@@ -120,7 +120,7 @@ _LOGGER = logging.getLogger(__name__)
120
120
 
121
121
  class Actuator(DeviceHeat): # 3EF0, 3EF1 (for 10:/13:)
122
122
  # .I --- 13:109598 --:------ 13:109598 3EF0 003 00C8FF # event-driven, 00/C8
123
- # RP --- 13:109598 18:002563 --:------ 0008 002 00C8 # 00/C8, as abobe
123
+ # RP --- 13:109598 18:002563 --:------ 0008 002 00C8 # 00/C8, as above
124
124
  # RP --- 13:109598 18:002563 --:------ 3EF1 007 0000BF-00BFC8FF # 00/C8, as above
125
125
 
126
126
  # RP --- 10:048122 18:140805 --:------ 3EF1 007 007FFF-003C2A10 # 10:s only RP, always 7FFF
@@ -668,11 +668,8 @@ 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
- # should fix src/ramses_rf/database.py _add_record try/except when activating next line
672
671
  if self._gwy.msg_db:
673
- self._add_record(
674
- address=self.addr, code=Code._3220, verb="RP"
675
- ) # << essential?
672
+ self._add_record(address=self.addr, code=Code._3220, verb="RP")
676
673
  # adds a "sim" RP opentherm_msg to the SQLite MessageIndex with code _3220
677
674
  # causes exc when fetching ALL, when no "real" msg was added to _msgs_. We skip those.
678
675
  else:
@@ -1411,7 +1408,7 @@ class UfhCircuit(Child, Entity): # FIXME
1411
1408
  def __init__(self, ufc: UfhController, ufh_idx: str) -> None:
1412
1409
  super().__init__(ufc._gwy)
1413
1410
 
1414
- # FIXME: ZZZ entities must know their parent device ID and their own idx
1411
+ # FIXME: gwy.msg_db entities must know their parent device ID and their own idx
1415
1412
  self._z_id = ufc.id
1416
1413
  self._z_idx = ufh_idx
1417
1414
 
ramses_rf/entity_base.py CHANGED
@@ -67,8 +67,7 @@ if TYPE_CHECKING:
67
67
 
68
68
 
69
69
  _QOS_TX_LIMIT = 12 # TODO: needs work
70
- _ID_SLICE = 9 # base address only, legacy _msgs 9
71
- _SQL_SLICE = 12 # msg_db dst field query 12
70
+ _ID_SLICE = 9
72
71
  _SZ_LAST_PKT: Final = "last_msg"
73
72
  _SZ_NEXT_DUE: Final = "next_due"
74
73
  _SZ_TIMEOUT: Final = "timeout"
@@ -248,6 +247,7 @@ class _MessageDB(_Entity):
248
247
  "For %s (z_id %s) add msg %s, src %s, dst %s to msg_db.",
249
248
  self.id,
250
249
  self._z_id,
250
+ msg,
251
251
  msg.src,
252
252
  msg.dst,
253
253
  )
@@ -291,7 +291,7 @@ class _MessageDB(_Entity):
291
291
 
292
292
  @property
293
293
  def _msg_list(self) -> list[Message]:
294
- """Return a flattened list of all messages logged on device."""
294
+ """Return a flattened list of all messages logged on this device."""
295
295
  # (only) used in gateway.py#get_state() and in tests/tests/test_eavesdrop_schema.py
296
296
  if self._gwy.msg_db:
297
297
  msg_list_qry: list[Message] = []
@@ -302,6 +302,8 @@ class _MessageDB(_Entity):
302
302
  # safeguard against lookup failures ("sim" packets?)
303
303
  msg_list_qry.append(self._msgs[c])
304
304
  else:
305
+ # evohome has these errors
306
+ # _msg_list could not fetch self._msgs[7FFF] for 18:072981 (z_id 18:072981)
305
307
  _LOGGER.debug(
306
308
  "_msg_list could not fetch self._msgs[%s] for %s (z_id %s)",
307
309
  c,
@@ -428,7 +430,7 @@ class _MessageDB(_Entity):
428
430
  :param code: filter messages by Code or a tuple of Codes, optional
429
431
  :param verb: filter on I, RQ, RP, optional, only with a single Code
430
432
  :param key: value keyword to retrieve, not together with verb RQ
431
- :param kwargs: not used for now
433
+ :param kwargs: extra filter, e.g. zone_idx='01'
432
434
  :return: a dict containing key: value pairs, or a list of those
433
435
  """
434
436
  assert not isinstance(code, tuple) or verb is None, (
@@ -444,7 +446,9 @@ class _MessageDB(_Entity):
444
446
  key = None
445
447
  try:
446
448
  if self._gwy.msg_db: # central SQLite MessageIndex, use verb= kwarg
447
- code = Code(self._msg_qry_by_code_key(code, key, verb=verb))
449
+ code = Code(
450
+ self._msg_qry_by_code_key(code, key, **kwargs, verb=verb)
451
+ )
448
452
  msg = self._msgs.get(code)
449
453
  else: # deprecated lookup in nested _msgz
450
454
  msgs = self._msgz[code][verb]
@@ -456,7 +460,9 @@ class _MessageDB(_Entity):
456
460
  msgs = [m for m in self._msgs.values() if m.code in code]
457
461
  msg = max(msgs) if msgs else None
458
462
  # return highest = latest? value found in code:value pairs
459
- else:
463
+ else: # single Code
464
+ # for Zones, this doesn't work, returns first result = often wrong
465
+ # TODO fix in _msg_qry_by_code_key()
460
466
  msg = self._msgs.get(code)
461
467
 
462
468
  return self._msg_value_msg(msg, key=key, **kwargs)
@@ -531,19 +537,37 @@ class _MessageDB(_Entity):
531
537
 
532
538
  :return: list of Codes or empty list when query returned empty
533
539
  """
540
+
534
541
  if self._gwy.msg_db:
535
542
  # SQLite query on MessageIndex
536
- sql = """
537
- SELECT code from messages WHERE verb in (' I', 'RP')
538
- AND (src = ? OR dst = ?)
539
- """
540
543
  res: list[Code] = []
544
+ if self.id[_ID_SLICE:] == "_HW":
545
+ sql = """
546
+ SELECT code from messages WHERE
547
+ verb in (' I', 'RP')
548
+ AND (src = ? OR dst = ?)
549
+ AND (ctx IN ('FC', 'FA', 'F9', 'FA') OR plk LIKE ?)
550
+ """
551
+ _ctx_qry = "%dhw_idx%" # syntax error ?
552
+ else:
553
+ sql = """
554
+ SELECT code from messages WHERE
555
+ verb in (' I', 'RP')
556
+ AND (src = ? OR dst = ?)
557
+ AND ctx LIKE ?
558
+ """
559
+ _ctx_qry = f"%{self.id[_ID_SLICE + 1 :]}%"
541
560
 
542
561
  for rec in self._gwy.msg_db.qry_field(
543
- sql, (self.id[:_SQL_SLICE], self.id[:_SQL_SLICE])
562
+ sql, (self.id[:_ID_SLICE], self.id[:_ID_SLICE], _ctx_qry)
544
563
  ):
545
- _LOGGER.debug("Fetched from index: %s", rec[0])
546
- # Example: "Fetched from index: code 1FD4"
564
+ _LOGGER.debug(
565
+ "Fetched from index: %s for %s (z_id %s)",
566
+ rec[0],
567
+ self.id,
568
+ self._z_id,
569
+ )
570
+ # Example: "Fetched from index: code 1FD4 for 01:123456 (z_id 01)"
547
571
  res.append(Code(str(rec[0])))
548
572
  return res
549
573
  else:
@@ -566,32 +590,48 @@ class _MessageDB(_Entity):
566
590
  :return: Code of most recent query result message or None when query returned empty
567
591
  """
568
592
  if self._gwy.msg_db:
569
- code_qry: str = ""
593
+ code_qry: str = "= "
570
594
  if code is None:
571
- code_qry = "*"
595
+ code_qry = "LIKE '%'" # wildcard
572
596
  elif isinstance(code, tuple):
573
597
  for cd in code:
574
598
  code_qry += f"'{str(cd)}' OR code = '"
575
599
  code_qry = code_qry[:-13] # trim last OR
576
600
  else:
577
- code_qry = str(code)
578
- key = "*" if key is None else f"%{key}%"
601
+ code_qry += str(code)
579
602
  if kwargs["verb"] and kwargs["verb"] in (" I", "RP"):
580
603
  vb = f"('{str(kwargs['verb'])}',)"
581
604
  else:
582
605
  vb = "(' I', 'RP',)"
606
+ ctx_qry = "%"
607
+ if kwargs["zone_idx"]:
608
+ ctx_qry = f"%{kwargs['zone_idx']}%"
609
+ elif kwargs["dhw_idx"]: # DHW
610
+ ctx_qry = f"%{kwargs['dhw_idx']}%"
611
+ key_qry = "%" if key is None else f"%{key}%"
612
+
583
613
  # SQLite query on MessageIndex
584
614
  sql = """
585
- SELECT dtm, code from messages WHERE verb in ?
615
+ SELECT dtm, code from messages WHERE
616
+ verb in ?
586
617
  AND (src = ? OR dst = ?)
587
- AND (code = ?)
618
+ AND (code ?)
619
+ AND (ctx LIKE ?)
588
620
  AND (plk LIKE ?)
589
621
  """
590
622
  latest: dt = dt(0, 0, 0)
591
623
  res = None
592
624
 
593
625
  for rec in self._gwy.msg_db.qry_field(
594
- sql, (vb, self.id[:_SQL_SLICE], self.id[:_SQL_SLICE], code_qry, key)
626
+ sql,
627
+ (
628
+ vb,
629
+ self.id[:_ID_SLICE],
630
+ self.id[:_ID_SLICE],
631
+ code_qry,
632
+ ctx_qry,
633
+ key_qry,
634
+ ),
595
635
  ):
596
636
  _LOGGER.debug(
597
637
  "_msg_qry_by_code_key fetched rec: %s, code: %s", rec, code_qry
@@ -654,7 +694,7 @@ class _MessageDB(_Entity):
654
694
  # """SELECT code from messages WHERE verb in (' I', 'RP') AND (src = ? OR dst = ?)
655
695
  # AND (code = '31DA' OR ...) AND (plk LIKE '%{SZ_FAN_INFO}%' OR ...)""" = 2 params
656
696
  for rec in self._gwy.msg_db.qry_field(
657
- sql, (self.id[:_SQL_SLICE], self.id[:_SQL_SLICE])
697
+ sql, (self.id[:_ID_SLICE], self.id[:_ID_SLICE])
658
698
  ):
659
699
  _pl = self._msgs[Code(rec[0])].payload
660
700
  # add payload dict to res(ults)
@@ -694,24 +734,38 @@ class _MessageDB(_Entity):
694
734
  # _LOGGER.warning("Missing MessageIndex")
695
735
  # raise NotImplementedError
696
736
 
697
- if self.id[:3] == "18:": # HGI, confirm this is correct, tests suggest so
698
- return {}
737
+ # if self.id[:3] == "18:": # HGI, confirm this is correct, tests suggest so
738
+ # return {}
699
739
 
700
- sql = """
701
- SELECT dtm from messages WHERE verb in (' I', 'RP') AND (src = ? OR dst = ?)
702
- """
703
-
704
- # handy routine to debug dict creation, see test_systems.py
740
+ # a routine to debug dict creation, see test_systems.py:
705
741
  # print(f"Create _msgs for {self.id}:")
706
742
  # results = self._gwy.msg_db._cu.execute("SELECT dtm, src, code from messages WHERE verb in (' I', 'RP') and code is '3150'")
707
743
  # for r in results:
708
744
  # print(r)
709
745
 
710
- _msg_dict = { # ? use ctx (context) instead of just the address?
746
+ if self.id[_ID_SLICE:] == "_HW":
747
+ sql = """
748
+ SELECT dtm from messages WHERE
749
+ verb in (' I', 'RP')
750
+ AND (src = ? OR dst = ?)
751
+ AND (ctx IN ('FC', 'FA', 'F9', 'FA') OR plk LIKE ?)
752
+ """
753
+ _ctx_qry = "%dhw_idx%"
754
+ # TODO add Children messages? self.ctl.dhw
755
+ else:
756
+ sql = """
757
+ SELECT dtm from messages WHERE
758
+ verb in (' I', 'RP')
759
+ AND (src = ? OR dst = ?)
760
+ AND ctx LIKE ?
761
+ """
762
+ _ctx_qry = f"%{self.id[_ID_SLICE + 1 :]}%"
763
+
764
+ _msg_dict = { # since 0.52.3 use ctx (context) instead of just the address
711
765
  m.code: m
712
766
  for m in self._gwy.msg_db.qry(
713
- sql, (self.id[:_SQL_SLICE], self.id[:_SQL_SLICE])
714
- ) # e.g. 01:123456_HW
767
+ sql, (self.id[:_ID_SLICE], self.id[:_ID_SLICE], _ctx_qry)
768
+ ) # e.g. 01:123456_HW, 01:123456_02 (Zone)
715
769
  }
716
770
  # if CTL, remove 3150, 3220 heat_demand, both are only stored on children
717
771
  # HACK
@@ -786,7 +840,7 @@ class _Discovery(_MessageDB):
786
840
  code: CODES_SCHEMA[code][SZ_NAME]
787
841
  for code in sorted(
788
842
  self._gwy.msg_db.get_rp_codes(
789
- (self.id[:_SQL_SLICE], self.id[:_SQL_SLICE])
843
+ (self.id[:_ID_SLICE], self.id[:_ID_SLICE])
790
844
  )
791
845
  )
792
846
  if self._is_not_deprecated_cmd(code)
@@ -818,7 +872,7 @@ class _Discovery(_MessageDB):
818
872
  AND (src = ? OR dst = ?)
819
873
  """
820
874
  for rec in self._gwy.msg_db.qry_field(
821
- sql, (self.id[:_SQL_SLICE], self.id[:_SQL_SLICE])
875
+ sql, (self.id[:_ID_SLICE], self.id[:_ID_SLICE])
822
876
  ):
823
877
  _LOGGER.debug("Fetched OT ctx from index: %s", rec[0])
824
878
  res.append(rec[0])
@@ -949,8 +1003,8 @@ class _Discovery(_MessageDB):
949
1003
  sql,
950
1004
  (
951
1005
  task[_SZ_COMMAND].code,
952
- self.tcs.id[:_ID_SLICE], # OK? not _SQL_SLICE?
953
- self.tcs.id[:_ID_SLICE], # OK? not _SQL_SLICE?
1006
+ self.tcs.id[:_ID_SLICE],
1007
+ self.tcs.id[:_ID_SLICE],
954
1008
  ),
955
1009
  )[0] # expect 1 Message in returned tuple
956
1010
  else: # TODO(eb) remove next Q1 2026
ramses_rf/system/zones.py CHANGED
@@ -233,7 +233,7 @@ class DhwZone(ZoneSchedule): # CS92A
233
233
  # def eavesdrop_dhw_sensor(this: Message, *, prev: Message | None = None) -> None:
234
234
  # """Eavesdrop packets, or pairs of packets, to maintain the system state.
235
235
 
236
- # There are only 2 ways to to find a controller's DHW sensor:
236
+ # There are only 2 ways to find a controller's DHW sensor:
237
237
  # 1. The 10A0 RQ/RP *from/to a 07:* (1x/4h) - reliable
238
238
  # 2. Use sensor temp matching - non-deterministic
239
239
 
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.52.2"
3
+ __version__ = "0.52.3"
4
4
  VERSION = __version__
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ramses_rf
3
- Version: 0.52.2
3
+ Version: 0.52.3
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
@@ -5,26 +5,26 @@ ramses_cli/discovery.py,sha256=MWoahBnAAVzfK2S7EDLsY2WYqN_ZK9L-lktrj8_4cb0,12978
5
5
  ramses_cli/utils/cat_slow.py,sha256=AhUpM5gnegCitNKU-JGHn-DrRzSi-49ZR1Qw6lxe_t8,607
6
6
  ramses_cli/utils/convert.py,sha256=D_YiCyX5na9pgC-_NhBlW9N1dgRKUK-uLtLBfofjzZM,1804
7
7
  ramses_rf/__init__.py,sha256=vp2TyFGqc1fGQHsevhmaw0QEmSSCnZx7fqizKiEwHtw,1245
8
- ramses_rf/binding_fsm.py,sha256=uZAOl3i19KCXqqlaLJWkEqMMP7NJBhVPW3xTikQD1fY,25996
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=pfCOt6wvABAaW275mNLoRnObFSQ9nT5d7PtDLMTvgr4,20340
10
+ ramses_rf/database.py,sha256=rMfEJvapc3Zh6SFqjPjntuuaYynuZiAdYTw1pcXsJPE,20344
11
11
  ramses_rf/dispatcher.py,sha256=YjEU-QrBLo9IfoEhJo2ikg_FxOaMYoWvzelr9Vi-JZ8,11398
12
- ramses_rf/entity_base.py,sha256=OPdQEeJ5zk49FKCFmRui4urFQVR2tLBS1iWCTWeZkNo,55712
12
+ ramses_rf/entity_base.py,sha256=uN8QGGv0cutCYTT-oyDkvzv4xpBEObWF4jo9ArtvbLE,57640
13
13
  ramses_rf/exceptions.py,sha256=mt_T7irqHSDKir6KLaf6oDglUIdrw0S40JbOrWJk5jc,3657
14
14
  ramses_rf/gateway.py,sha256=VYZqMppU_kDYhT3EUmqpHf0LuLXPMH7ASx9jylAopWE,21218
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=3xbuWkwVUOezgu-nlN98fGrnAgqtfivOcMVKjCTSBds,125
18
+ ramses_rf/version.py,sha256=E3rkZTOmbKgrnpOmzEdweYaquvnmc18LcdiXXdsnakM,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=hvTX32Evp23vqjXu-YHqTuneem4_QBRSxCkYR7dg4VI,54705
21
+ ramses_rf/device/heat.py,sha256=tjORTbjxBKFzgPQbfpLuj74IlrVtOmj9GpzslK9j0A0,54569
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
25
  ramses_rf/system/heat.py,sha256=3jaFEChU-HlWCRMY1y7u09s7AH4hT0pC63hnqwdmZOc,39223
26
26
  ramses_rf/system/schedule.py,sha256=Ts6tdZPTQLV5NkgwA73tPa5QUsnZNIIuYoKC-8VsXDk,18808
27
- ramses_rf/system/zones.py,sha256=9AH7ooN5QfiqvWuor2P1Dn8aILjQb2RWL9rWqDH1IjA,36075
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
@@ -32,9 +32,9 @@ ramses_tx/const.py,sha256=pnxq5upXvLUizv9Ye_I1llD9rAa3wddHgsSkc91AIUc,30300
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
35
- ramses_tx/gateway.py,sha256=8o9NyEAyIui3B6HA0R8o64u4U8NZFlNl0TQ6rhwMoCk,11369
35
+ ramses_tx/gateway.py,sha256=Fl6EqAUU2DnLOiA2_87sS7VPBEyxA1a_ICCmg55WMMA,11494
36
36
  ramses_tx/helpers.py,sha256=J4OCRckp3JshGQTvvqEskFjB1hPS7uA_opVsuIqmZds,32915
37
- ramses_tx/logger.py,sha256=qYbUoNPnPaFWKVsYvLG6uTVuPTdZ8HsMzBbGx0DpBqc,10177
37
+ 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
@@ -43,13 +43,13 @@ ramses_tx/protocol.py,sha256=9R3aCzuiWEyXmugmB5tyR_RhBlgfnpUXj7AsMP9BzzU,28867
43
43
  ramses_tx/protocol_fsm.py,sha256=ZKtehCr_4TaDdfdlfidFLJaOVTYtaEq5h4tLqNIhb9s,26827
44
44
  ramses_tx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
45
  ramses_tx/ramses.py,sha256=NG81GBNZlap-Gi9ac-r6OFE-KaHvXsgPDWy-I2Irr-4,53698
46
- ramses_tx/schemas.py,sha256=U8SkwbW41zac0MQe0NgP6qTUqymmbeCG-UHPEw_GAv0,13267
47
- ramses_tx/transport.py,sha256=kyvnVf2RpuA_8YRO6C4S60YHctDag0ywfoMzb0wWDGY,58552
46
+ ramses_tx/schemas.py,sha256=bqKW_V0bR6VbBD8ZQiBExNtVdXs0fryVKe3GEhupgIo,13424
47
+ ramses_tx/transport.py,sha256=oVClKNnCfHioIk5tF0TGmYspJhpggQ6EspOYeyBBR4g,58841
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=NEkkAJQW7Hx8GmvkR9BZpLSOi30OMi9Awth13sWXuTQ,123
51
- ramses_rf-0.52.2.dist-info/METADATA,sha256=Fm3vwa0FvFkMEHz44B8FDq0PjboTn_g9sGHyK7HiydA,4000
52
- ramses_rf-0.52.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
- ramses_rf-0.52.2.dist-info/entry_points.txt,sha256=NnyK29baOCNg8DinPYiZ368h7MTH7bgTW26z2A1NeIE,50
54
- ramses_rf-0.52.2.dist-info/licenses/LICENSE,sha256=-Kc35W7l1UkdiQ4314_yVWv7vDDrg7IrJfMLUiq6Nfs,1074
55
- ramses_rf-0.52.2.dist-info/RECORD,,
50
+ ramses_tx/version.py,sha256=OmZAmWYXnodOMX2FqbBZjLsKSaBVpA4Y37368OIox1o,123
51
+ ramses_rf-0.52.3.dist-info/METADATA,sha256=u8gl35WcjLN5rO1P_BTfchK23MayeJLDQxFEhHOM-v4,4000
52
+ ramses_rf-0.52.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
+ ramses_rf-0.52.3.dist-info/entry_points.txt,sha256=NnyK29baOCNg8DinPYiZ368h7MTH7bgTW26z2A1NeIE,50
54
+ ramses_rf-0.52.3.dist-info/licenses/LICENSE,sha256=-Kc35W7l1UkdiQ4314_yVWv7vDDrg7IrJfMLUiq6Nfs,1074
55
+ ramses_rf-0.52.3.dist-info/RECORD,,
ramses_tx/gateway.py CHANGED
@@ -34,6 +34,7 @@ from .schemas import (
34
34
  SZ_DISABLE_QOS,
35
35
  SZ_DISABLE_SENDING,
36
36
  SZ_ENFORCE_KNOWN_LIST,
37
+ SZ_LOG_ALL_MQTT,
37
38
  SZ_PACKET_LOG,
38
39
  SZ_PORT_CONFIG,
39
40
  SZ_PORT_NAME,
@@ -114,6 +115,7 @@ class Engine:
114
115
  self._exclude,
115
116
  )
116
117
  self._sqlite_index = kwargs.pop(SZ_SQLITE_INDEX, False) # default True?
118
+ self._log_all_mqtt = kwargs.pop(SZ_LOG_ALL_MQTT, False)
117
119
  self._kwargs: dict[str, Any] = kwargs # HACK
118
120
 
119
121
  self._engine_lock = Lock() # FIXME: threading lock, or asyncio lock?
@@ -197,6 +199,7 @@ class Engine:
197
199
  self._protocol,
198
200
  disable_sending=self._disable_sending,
199
201
  loop=self._loop,
202
+ log_all=self._log_all_mqtt,
200
203
  **pkt_source,
201
204
  **self._kwargs, # HACK: odd/misc params, e.g. comms_params
202
205
  )
ramses_tx/logger.py CHANGED
@@ -162,6 +162,14 @@ class StdOutFilter(logging.Filter): # record.levelno < logging.WARNING
162
162
  return record.levelno < logging.WARNING
163
163
 
164
164
 
165
+ class BlockMqttFilter(logging.Filter):
166
+ """Block mqtt traffic"""
167
+
168
+ def filter(self, record: logging.LogRecord) -> bool:
169
+ """Return True if the record is to be processed."""
170
+ return not record.getMessage().startswith("mq Rx: ")
171
+
172
+
165
173
  class TimedRotatingFileHandler(_TimedRotatingFileHandler):
166
174
  def __init__(self, *args: Any, **kwargs: Any) -> None:
167
175
  super().__init__(*args, **kwargs)
ramses_tx/schemas.py CHANGED
@@ -413,6 +413,7 @@ SZ_EVOFW_FLAG: Final = "evofw_flag"
413
413
  SZ_SQLITE_INDEX: Final = (
414
414
  "sqlite_index" # temporary 0.52.x SQLite dev config option in ramses_cc
415
415
  )
416
+ SZ_LOG_ALL_MQTT: Final = "log_all_mqtt"
416
417
  SZ_USE_REGEX: Final = "use_regex"
417
418
 
418
419
  SCH_ENGINE_DICT = {
@@ -427,6 +428,9 @@ SCH_ENGINE_DICT = {
427
428
  vol.Optional(
428
429
  SZ_SQLITE_INDEX, default=False
429
430
  ): bool, # temporary 0.52.x dev config option
431
+ vol.Optional(
432
+ SZ_LOG_ALL_MQTT, default=False
433
+ ): bool, # log all incoming MQTT traffic config option
430
434
  vol.Optional(SZ_USE_REGEX): dict, # vol.All(ConvertNullToDict(), dict),
431
435
  vol.Optional(SZ_COMMS_PARAMS): SCH_COMMS_PARAMS,
432
436
  }
ramses_tx/transport.py CHANGED
@@ -1024,6 +1024,7 @@ class PortTransport(_RegHackMixin, _FullTransport, _PortTransportAbstractor): #
1024
1024
 
1025
1025
  class MqttTransport(_FullTransport, _MqttTransportAbstractor):
1026
1026
  """Send/receive packets to/from ramses_esp via MQTT.
1027
+ For full RX logging, turn on debug logging.
1027
1028
 
1028
1029
  See: https://github.com/IndaloTech/ramses_esp
1029
1030
  """
@@ -1066,6 +1067,9 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
1066
1067
  self._max_tokens: float = self._MAX_TOKENS * 2 # allow for the initial burst
1067
1068
  self._num_tokens: float = self._MAX_TOKENS * 2
1068
1069
 
1070
+ # set log MQTT flag
1071
+ self._log_all = kwargs.pop("log_all", False)
1072
+
1069
1073
  # instantiate a paho mqtt client
1070
1074
  self.client = mqtt.Client(
1071
1075
  protocol=mqtt.MQTTv5, callback_api_version=CallbackAPIVersion.VERSION2
@@ -1283,8 +1287,9 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
1283
1287
 
1284
1288
  if _DBG_FORCE_FRAME_LOGGING:
1285
1289
  _LOGGER.warning("Rx: %s", msg.payload)
1286
- elif _LOGGER.getEffectiveLevel() == logging.INFO: # log for INFO not DEBUG
1287
- _LOGGER.info("Rx: %s", msg.payload)
1290
+ elif self._log_all and _LOGGER.getEffectiveLevel() == logging.INFO:
1291
+ # log for INFO not DEBUG
1292
+ _LOGGER.info("mq Rx: %s", msg.payload) # TODO remove mq marker?
1288
1293
 
1289
1294
  if msg.topic[-3:] != "/rx": # then, e.g. 'RAMSES/GATEWAY/18:017804'
1290
1295
  if msg.payload == b"offline":
@@ -1507,6 +1512,7 @@ async def transport_factory(
1507
1512
  disable_sending: bool | None = False,
1508
1513
  extra: dict[str, Any] | None = None,
1509
1514
  loop: asyncio.AbstractEventLoop | None = None,
1515
+ log_all: bool = False,
1510
1516
  **kwargs: Any, # HACK: odd/misc params
1511
1517
  ) -> RamsesTransportT:
1512
1518
  """Create and return a Ramses-specific async packet Transport."""
@@ -1558,19 +1564,24 @@ async def transport_factory(
1558
1564
  "Packet source must be exactly one of: packet_dict, packet_log, port_name"
1559
1565
  )
1560
1566
 
1567
+ # File
1561
1568
  if (pkt_source := packet_log or packet_dict) is not None:
1562
1569
  return FileTransport(pkt_source, protocol, extra=extra, loop=loop, **kwargs)
1563
1570
 
1564
1571
  assert port_name is not None # mypy check
1565
1572
  assert port_config is not None # mypy check
1566
1573
 
1574
+ # MQTT
1567
1575
  if port_name[:4] == "mqtt": # TODO: handle disable_sending
1568
- transport = MqttTransport(port_name, protocol, extra=extra, loop=loop, **kwargs)
1576
+ transport = MqttTransport(
1577
+ port_name, protocol, extra=extra, loop=loop, log_all=log_all, **kwargs
1578
+ )
1569
1579
 
1570
1580
  # TODO: remove this? better to invoke timeout after factory returns?
1571
1581
  await protocol.wait_for_connection_made(timeout=_DEFAULT_TIMEOUT_MQTT)
1572
1582
  return transport
1573
1583
 
1584
+ # Serial
1574
1585
  ser_instance = get_serial_instance(port_name, port_config)
1575
1586
 
1576
1587
  if os.name == "nt" or ser_instance.portstr[:7] in ("rfc2217", "socket:"):
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.52.2"
3
+ __version__ = "0.52.3"
4
4
  VERSION = __version__