ramses-rf 0.52.3__py3-none-any.whl → 0.52.5__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_tx/protocol.py CHANGED
@@ -66,7 +66,7 @@ class _BaseProtocol(asyncio.Protocol):
66
66
  self._msg_handler = msg_handler
67
67
  self._msg_handlers: list[MsgHandlerT] = []
68
68
 
69
- self._transport: RamsesTransportT = None # type: ignore[assignment]
69
+ self._transport: RamsesTransportT | None = None
70
70
  self._loop = asyncio.get_running_loop()
71
71
 
72
72
  self._pause_writing = False # FIXME: Start in R/O mode as no connection yet?
@@ -80,6 +80,9 @@ class _BaseProtocol(asyncio.Protocol):
80
80
 
81
81
  self._is_evofw3: bool | None = None
82
82
 
83
+ self._active_hgi: DeviceIdT | None = None
84
+ self._context: ProtocolContext | None = None
85
+
83
86
  @property
84
87
  def hgi_id(self) -> DeviceIdT:
85
88
  return HGI_DEV_ADDR.id
@@ -140,7 +143,16 @@ class _BaseProtocol(asyncio.Protocol):
140
143
  received or the connection was aborted or closed).
141
144
  """
142
145
 
143
- assert self._wait_connection_lost # mypy
146
+ # FIX: Check if _wait_connection_lost exists before asserting
147
+ # This handles cases where connection was never fully established (e.g. timeout)
148
+ if not self._wait_connection_lost:
149
+ _LOGGER.debug(
150
+ "connection_lost called but no connection was established (ignoring)"
151
+ )
152
+ # Reset the connection made future for next attempt
153
+ if self._wait_connection_made.done():
154
+ self._wait_connection_made = self._loop.create_future()
155
+ return
144
156
 
145
157
  if self._wait_connection_lost.done(): # BUG: why is callback invoked twice?
146
158
  return
@@ -199,6 +211,10 @@ class _BaseProtocol(asyncio.Protocol):
199
211
 
200
212
  self._pause_writing = False
201
213
 
214
+ async def _send_impersonation_alert(self, cmd: Command) -> None:
215
+ """Allow the Protocol to send an impersonation alert (stub)."""
216
+ return
217
+
202
218
  async def send_cmd(
203
219
  self,
204
220
  cmd: Command,
@@ -207,22 +223,64 @@ class _BaseProtocol(asyncio.Protocol):
207
223
  gap_duration: float = DEFAULT_GAP_DURATION,
208
224
  num_repeats: int = DEFAULT_NUM_REPEATS,
209
225
  priority: Priority = Priority.DEFAULT,
210
- qos: QosParams = DEFAULT_QOS,
226
+ qos: QosParams = DEFAULT_QOS, # max_retries, timeout, wait_for_reply
211
227
  ) -> Packet:
212
- """This is the wrapper for self._send_cmd(cmd)."""
228
+ """Send a Command with Qos (with retries, until success or ProtocolError).
213
229
 
214
- # if not self._transport:
215
- # raise exc.ProtocolSendFailed("There is no connected Transport")
230
+ Returns the Command's response Packet or the Command echo if a response is not
231
+ expected (e.g. sending an RP).
216
232
 
217
- if _DBG_FORCE_LOG_PACKETS:
218
- _LOGGER.warning(f"QUEUED: {cmd}")
219
- else:
220
- _LOGGER.debug(f"QUEUED: {cmd}")
233
+ If wait_for_reply is True, return the RQ's RP (or W's I), or raise an exception
234
+ if one doesn't arrive. If it is False, return the echo of the Command only. If
235
+ it is None (the default), act as True for RQs, and False for all other Commands.
221
236
 
222
- if self._pause_writing:
223
- raise exc.ProtocolError("The Protocol is currently read-only/paused")
237
+ num_repeats is # of times to send the Command, in addition to the fist transmit,
238
+ with gap_duration seconds between each transmission. If wait_for_reply is True,
239
+ then num_repeats is ignored.
240
+
241
+ Commands are queued and sent FIFO, except higher-priority Commands are always
242
+ sent first.
243
+
244
+ Will raise:
245
+ ProtocolSendFailed: tried to Tx Command, but didn't get echo/reply
246
+ ProtocolError: didn't attempt to Tx Command for some reason
247
+ """
224
248
 
225
- return await self._send_cmd(
249
+ assert gap_duration == DEFAULT_GAP_DURATION
250
+ assert 0 <= num_repeats <= 3 # if QoS, only Tx x1, with no repeats
251
+
252
+ # FIX: Patch command with actual HGI ID if it uses the default placeholder
253
+ # NOTE: HGI80s (TI 3410) require the default ID (18:000730), or they will silent-fail
254
+ if (
255
+ self._active_hgi
256
+ and self._is_evofw3 # Only patch if using evofw3 (not HGI80)
257
+ and cmd._addrs[0].id == HGI_DEV_ADDR.id
258
+ and self._active_hgi != HGI_DEV_ADDR.id
259
+ ):
260
+ # The command uses the default 18:000730, but we know the real ID.
261
+ # Reconstruct the command string with the correct address.
262
+
263
+ # Get current addresses as strings
264
+ new_addrs = [a.id for a in cmd._addrs]
265
+
266
+ # ONLY patch the Source Address (Index 0).
267
+ # Leave Dest (Index 1/2) alone to avoid breaking tests that expect 18:000730 there.
268
+ new_addrs[0] = self._active_hgi
269
+
270
+ new_frame = f"{cmd.verb} {cmd.seqn} {new_addrs[0]} {new_addrs[1]} {new_addrs[2]} {cmd.code} {int(cmd.len_):03d} {cmd.payload}"
271
+ cmd = Command(new_frame)
272
+
273
+ if qos and not self._context:
274
+ _LOGGER.warning(f"{cmd} < QoS is currently disabled by this Protocol")
275
+
276
+ if cmd.src.id != self.hgi_id: # Was HGI_DEV_ADDR.id
277
+ await self._send_impersonation_alert(cmd)
278
+
279
+ if qos.wait_for_reply and num_repeats:
280
+ _LOGGER.warning(f"{cmd} < num_repeats set to 0, as wait_for_reply is True")
281
+ num_repeats = 0 # the lesser crime over wait_for_reply=False
282
+
283
+ pkt = await self._send_cmd( # may: raise ProtocolError/ProtocolSendFailed
226
284
  cmd,
227
285
  gap_duration=gap_duration,
228
286
  num_repeats=num_repeats,
@@ -230,6 +288,11 @@ class _BaseProtocol(asyncio.Protocol):
230
288
  qos=qos,
231
289
  )
232
290
 
291
+ if not pkt: # HACK: temporary workaround for returning None
292
+ raise exc.ProtocolSendFailed(f"Failed to send command: {cmd} (REPORT THIS)")
293
+
294
+ return pkt
295
+
233
296
  async def _send_cmd(
234
297
  self,
235
298
  cmd: Command,
@@ -249,6 +312,10 @@ class _BaseProtocol(asyncio.Protocol):
249
312
  self, frame: str, num_repeats: int = 0, gap_duration: float = 0.0
250
313
  ) -> None: # _send_frame() -> transport
251
314
  """Write to the transport."""
315
+
316
+ if self._transport is None:
317
+ raise exc.ProtocolSendFailed("Transport is not connected")
318
+
252
319
  await self._transport.write_frame(frame)
253
320
  for _ in range(num_repeats - 1):
254
321
  await asyncio.sleep(gap_duration)
@@ -555,8 +622,17 @@ class PortProtocol(_DeviceIdFilterMixin, _BaseProtocol):
555
622
  super().connection_made(transport)
556
623
  # TODO: needed? self.resume_writing()
557
624
 
558
- self._set_active_hgi(self._transport.get_extra_info(SZ_ACTIVE_HGI))
559
- self._is_evofw3 = self._transport.get_extra_info(SZ_IS_EVOFW3)
625
+ # ROBUSTNESS FIX: Ensure self._transport is set even if the wait future was cancelled
626
+ if self._transport is None:
627
+ _LOGGER.warning(
628
+ f"{self}: Transport bound after wait cancelled (late connection)"
629
+ )
630
+ self._transport = transport
631
+
632
+ # Safe access with check (optional but recommended)
633
+ if self._transport:
634
+ self._set_active_hgi(self._transport.get_extra_info(SZ_ACTIVE_HGI))
635
+ self._is_evofw3 = self._transport.get_extra_info(SZ_IS_EVOFW3)
560
636
 
561
637
  if not self._context:
562
638
  return
@@ -583,7 +659,7 @@ class PortProtocol(_DeviceIdFilterMixin, _BaseProtocol):
583
659
  self._context.pause_writing()
584
660
 
585
661
  def resume_writing(self) -> None:
586
- """Inform the FSM that the Protocol has been paused."""
662
+ """Inform the FSM that the Protocol has been resumed."""
587
663
 
588
664
  super().resume_writing()
589
665
  if self._context:
@@ -597,7 +673,7 @@ class PortProtocol(_DeviceIdFilterMixin, _BaseProtocol):
597
673
  self._context.pkt_received(pkt)
598
674
 
599
675
  async def _send_impersonation_alert(self, cmd: Command) -> None:
600
- """Send an puzzle packet warning that impersonation is occurring."""
676
+ """Send a puzzle packet warning that impersonation is occurring."""
601
677
 
602
678
  if _DBG_DISABLE_IMPERSONATION_ALERTS:
603
679
  return
@@ -656,6 +732,8 @@ class PortProtocol(_DeviceIdFilterMixin, _BaseProtocol):
656
732
  # f"{self}: Failed to send {cmd._hdr}: excluded by list"
657
733
  # )
658
734
 
735
+ assert self._context
736
+
659
737
  try:
660
738
  return await self._context.send_cmd(send_cmd, cmd, priority, qos)
661
739
  # except InvalidStateError as err: # TODO: handle InvalidStateError separately
@@ -701,9 +779,6 @@ class PortProtocol(_DeviceIdFilterMixin, _BaseProtocol):
701
779
  if qos and not self._context:
702
780
  _LOGGER.warning(f"{cmd} < QoS is currently disabled by this Protocol")
703
781
 
704
- if cmd.src.id != HGI_DEV_ADDR.id: # or actual HGI addr
705
- await self._send_impersonation_alert(cmd)
706
-
707
782
  if qos.wait_for_reply and num_repeats:
708
783
  _LOGGER.warning(f"{cmd} < num_repeats set to 0, as wait_for_reply is True")
709
784
  num_repeats = 0 # the lesser crime over wait_for_reply=False
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
@@ -103,7 +103,7 @@ if TYPE_CHECKING:
103
103
 
104
104
 
105
105
  _DEFAULT_TIMEOUT_PORT: Final[float] = 3
106
- _DEFAULT_TIMEOUT_MQTT: Final[float] = 9
106
+ _DEFAULT_TIMEOUT_MQTT: Final[float] = 60 # Updated from 9s to 60s for robustness
107
107
 
108
108
  _SIGNATURE_GAP_SECS = 0.05
109
109
  _SIGNATURE_MAX_TRYS = 40 # was: 24
@@ -1368,7 +1368,13 @@ class MqttTransport(_FullTransport, _MqttTransportAbstractor):
1368
1368
  )
1369
1369
  # FIXME: convert all dt early, and convert to aware, i.e. dt.now().astimezone()
1370
1370
 
1371
- self._frame_read(dtm.isoformat(), _normalise(payload["msg"]))
1371
+ try:
1372
+ self._frame_read(dtm.isoformat(), _normalise(payload["msg"]))
1373
+ except exc.TransportError:
1374
+ # If the transport is closing, we expect this error and can safely ignore it
1375
+ # prevents "Uncaught thread exception" in paho.mqtt client
1376
+ if not self._closing:
1377
+ raise
1372
1378
 
1373
1379
  async def write_frame(self, frame: str, disable_tx_limits: bool = False) -> None:
1374
1380
  """Transmit a frame via the underlying handler (e.g. serial port, MQTT).
@@ -1572,13 +1578,31 @@ async def transport_factory(
1572
1578
  assert port_config is not None # mypy check
1573
1579
 
1574
1580
  # MQTT
1575
- if port_name[:4] == "mqtt": # TODO: handle disable_sending
1581
+ if port_name[:4] == "mqtt":
1582
+ # Check for custom timeout in kwargs, fallback to constant
1583
+ mqtt_timeout = kwargs.get("timeout", _DEFAULT_TIMEOUT_MQTT)
1584
+
1576
1585
  transport = MqttTransport(
1577
- port_name, protocol, extra=extra, loop=loop, log_all=log_all, **kwargs
1586
+ port_name,
1587
+ protocol,
1588
+ disable_sending=bool(
1589
+ disable_sending
1590
+ ), # Feature Added: handled disable_sending
1591
+ extra=extra,
1592
+ loop=loop,
1593
+ log_all=log_all,
1594
+ **kwargs,
1578
1595
  )
1579
1596
 
1580
- # TODO: remove this? better to invoke timeout after factory returns?
1581
- await protocol.wait_for_connection_made(timeout=_DEFAULT_TIMEOUT_MQTT)
1597
+ try:
1598
+ # Robustness Fix: Wait with timeout, handle failure gracefully
1599
+ await protocol.wait_for_connection_made(timeout=mqtt_timeout)
1600
+ except Exception:
1601
+ # CRITICAL FIX: Close the transport if setup fails to prevent "Zombie" callbacks
1602
+ # This prevents the "AttributeError: 'NoneType'..." crash later on
1603
+ transport.close()
1604
+ raise
1605
+
1582
1606
  return transport
1583
1607
 
1584
1608
  # Serial
@@ -1598,4 +1622,5 @@ async def transport_factory(
1598
1622
 
1599
1623
  # TODO: remove this? better to invoke timeout after factory returns?
1600
1624
  await protocol.wait_for_connection_made(timeout=_DEFAULT_TIMEOUT_PORT)
1625
+ # pytest-cov times out in virtual_rf.py when set below 30.0 on GitHub Actions
1601
1626
  return transport
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.3"
3
+ __version__ = "0.52.5"
4
4
  VERSION = __version__
@@ -1,55 +0,0 @@
1
- ramses_cli/__init__.py,sha256=uvGzWqOf4avvgzxJNSLFWEelIWqSZ-AeLAZzg5x58bc,397
2
- ramses_cli/client.py,sha256=NTLhHhTiYmp7nyE3vqIFR9zjwfot1wdIT7QuMUgubD0,20350
3
- ramses_cli/debug.py,sha256=vgR0lOHoYjWarN948dI617WZZGNuqHbeq6Tc16Da7b4,608
4
- ramses_cli/discovery.py,sha256=MWoahBnAAVzfK2S7EDLsY2WYqN_ZK9L-lktrj8_4cb0,12978
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=vp2TyFGqc1fGQHsevhmaw0QEmSSCnZx7fqizKiEwHtw,1245
8
- ramses_rf/binding_fsm.py,sha256=fuqvcc9YW-wr8SPH8zadpPqrHAvzl_eeWF-IBtlLppY,26632
9
- ramses_rf/const.py,sha256=L3z31CZ-xqno6oZp_h-67CB_5tDDqTwSWXsqRtsjMcs,5460
10
- ramses_rf/database.py,sha256=rMfEJvapc3Zh6SFqjPjntuuaYynuZiAdYTw1pcXsJPE,20344
11
- ramses_rf/dispatcher.py,sha256=YjEU-QrBLo9IfoEhJo2ikg_FxOaMYoWvzelr9Vi-JZ8,11398
12
- ramses_rf/entity_base.py,sha256=uN8QGGv0cutCYTT-oyDkvzv4xpBEObWF4jo9ArtvbLE,57640
13
- ramses_rf/exceptions.py,sha256=mt_T7irqHSDKir6KLaf6oDglUIdrw0S40JbOrWJk5jc,3657
14
- ramses_rf/gateway.py,sha256=VYZqMppU_kDYhT3EUmqpHf0LuLXPMH7ASx9jylAopWE,21218
15
- ramses_rf/helpers.py,sha256=TNk_QkpIOB3alOp1sqnA9LOzi4fuDCeapNlW3zTzNas,4250
16
- ramses_rf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- ramses_rf/schemas.py,sha256=jvpx0hZiMhaogyxbxnrbxS7m2GekKK4DFfVTNs7h-WQ,13476
18
- ramses_rf/version.py,sha256=E3rkZTOmbKgrnpOmzEdweYaquvnmc18LcdiXXdsnakM,125
19
- ramses_rf/device/__init__.py,sha256=sUbH5dhbYFXSoM_TPFRutpRutBRpup7_cQ9smPtDTy8,4858
20
- ramses_rf/device/base.py,sha256=Yx0LZwMEb49naY8FolZ8HEBFb6XCPQBTVN_TWyO2nKg,17777
21
- ramses_rf/device/heat.py,sha256=tjORTbjxBKFzgPQbfpLuj74IlrVtOmj9GpzslK9j0A0,54569
22
- ramses_rf/device/hvac.py,sha256=gdpVACUvtq6ERMI0mwRhqIJtKYEmybzJA2NeyN1ELrs,48431
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=3jaFEChU-HlWCRMY1y7u09s7AH4hT0pC63hnqwdmZOc,39223
26
- ramses_rf/system/schedule.py,sha256=Ts6tdZPTQLV5NkgwA73tPa5QUsnZNIIuYoKC-8VsXDk,18808
27
- ramses_rf/system/zones.py,sha256=YN_HAbeaa2YioUOjafEpp-0IHmIwmKNSK_77pPcjtns,36072
28
- ramses_tx/__init__.py,sha256=sqnjM7pUGJDmec6igTtKViSB8FLX49B5gwhAmcY9ERY,3596
29
- ramses_tx/address.py,sha256=F5ZE-EbPNNom1fW9XXUILvD7DYSMBxNJvsHVliT5gjw,8452
30
- ramses_tx/command.py,sha256=tqVECwd_QokEcRv2MSk7TUU4JSBzCZcJh1eQ0jIGgoY,125122
31
- ramses_tx/const.py,sha256=pnxq5upXvLUizv9Ye_I1llD9rAa3wddHgsSkc91AIUc,30300
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=GzNsXr15YLeidJYGtk_xPqsZQh4ehDDlUCtT6rTDhT8,22046
35
- ramses_tx/gateway.py,sha256=Fl6EqAUU2DnLOiA2_87sS7VPBEyxA1a_ICCmg55WMMA,11494
36
- ramses_tx/helpers.py,sha256=J4OCRckp3JshGQTvvqEskFjB1hPS7uA_opVsuIqmZds,32915
37
- ramses_tx/logger.py,sha256=1iKRHKUaqHqGd76CkE_6mCVR0sYODtxshRRwfY61fTk,10426
38
- ramses_tx/message.py,sha256=-moQ8v3HVlNSl-x3U0DDfDcj8WQ7vLqclMNxsohbmnw,13449
39
- ramses_tx/opentherm.py,sha256=58PXz9l5x8Ou6Fm3y-R_UnGHCYahoi2RKIDdYStUMzk,42378
40
- ramses_tx/packet.py,sha256=_qHiPFWpQpKueZOgf1jJ93Y09iZjo3LZWStLglVkXg4,7370
41
- ramses_tx/parsers.py,sha256=Z0ochrNyO2l8SAP0oJxN-tx2nJluEaNP_uJCIm_lsA8,111279
42
- ramses_tx/protocol.py,sha256=9R3aCzuiWEyXmugmB5tyR_RhBlgfnpUXj7AsMP9BzzU,28867
43
- ramses_tx/protocol_fsm.py,sha256=ZKtehCr_4TaDdfdlfidFLJaOVTYtaEq5h4tLqNIhb9s,26827
44
- ramses_tx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- ramses_tx/ramses.py,sha256=NG81GBNZlap-Gi9ac-r6OFE-KaHvXsgPDWy-I2Irr-4,53698
46
- ramses_tx/schemas.py,sha256=bqKW_V0bR6VbBD8ZQiBExNtVdXs0fryVKe3GEhupgIo,13424
47
- ramses_tx/transport.py,sha256=oVClKNnCfHioIk5tF0TGmYspJhpggQ6EspOYeyBBR4g,58841
48
- ramses_tx/typed_dicts.py,sha256=w-0V5t2Q3GiNUOrRAWiW9GtSwbta_7luME6GfIb1zhI,10869
49
- ramses_tx/typing.py,sha256=eF2SlPWhNhEFQj6WX2AhTXiyRQVXYnFutiepllYl2rI,5042
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,,