ramses-rf 0.22.2__py3-none-any.whl → 0.51.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.
Files changed (72) hide show
  1. ramses_cli/__init__.py +18 -0
  2. ramses_cli/client.py +597 -0
  3. ramses_cli/debug.py +20 -0
  4. ramses_cli/discovery.py +405 -0
  5. ramses_cli/utils/cat_slow.py +17 -0
  6. ramses_cli/utils/convert.py +60 -0
  7. ramses_rf/__init__.py +31 -10
  8. ramses_rf/binding_fsm.py +787 -0
  9. ramses_rf/const.py +124 -105
  10. ramses_rf/database.py +297 -0
  11. ramses_rf/device/__init__.py +69 -39
  12. ramses_rf/device/base.py +187 -376
  13. ramses_rf/device/heat.py +540 -552
  14. ramses_rf/device/hvac.py +286 -171
  15. ramses_rf/dispatcher.py +153 -177
  16. ramses_rf/entity_base.py +478 -361
  17. ramses_rf/exceptions.py +82 -0
  18. ramses_rf/gateway.py +378 -514
  19. ramses_rf/helpers.py +57 -19
  20. ramses_rf/py.typed +0 -0
  21. ramses_rf/schemas.py +148 -194
  22. ramses_rf/system/__init__.py +16 -23
  23. ramses_rf/system/faultlog.py +363 -0
  24. ramses_rf/system/heat.py +295 -302
  25. ramses_rf/system/schedule.py +312 -198
  26. ramses_rf/system/zones.py +318 -238
  27. ramses_rf/version.py +2 -8
  28. ramses_rf-0.51.1.dist-info/METADATA +72 -0
  29. ramses_rf-0.51.1.dist-info/RECORD +55 -0
  30. {ramses_rf-0.22.2.dist-info → ramses_rf-0.51.1.dist-info}/WHEEL +1 -2
  31. ramses_rf-0.51.1.dist-info/entry_points.txt +2 -0
  32. {ramses_rf-0.22.2.dist-info → ramses_rf-0.51.1.dist-info/licenses}/LICENSE +1 -1
  33. ramses_tx/__init__.py +160 -0
  34. {ramses_rf/protocol → ramses_tx}/address.py +65 -59
  35. ramses_tx/command.py +1454 -0
  36. ramses_tx/const.py +903 -0
  37. ramses_tx/exceptions.py +92 -0
  38. {ramses_rf/protocol → ramses_tx}/fingerprints.py +56 -15
  39. {ramses_rf/protocol → ramses_tx}/frame.py +132 -131
  40. ramses_tx/gateway.py +338 -0
  41. ramses_tx/helpers.py +883 -0
  42. {ramses_rf/protocol → ramses_tx}/logger.py +67 -53
  43. {ramses_rf/protocol → ramses_tx}/message.py +155 -191
  44. ramses_tx/opentherm.py +1260 -0
  45. ramses_tx/packet.py +210 -0
  46. ramses_tx/parsers.py +2957 -0
  47. ramses_tx/protocol.py +801 -0
  48. ramses_tx/protocol_fsm.py +672 -0
  49. ramses_tx/py.typed +0 -0
  50. {ramses_rf/protocol → ramses_tx}/ramses.py +262 -185
  51. {ramses_rf/protocol → ramses_tx}/schemas.py +150 -133
  52. ramses_tx/transport.py +1471 -0
  53. ramses_tx/typed_dicts.py +492 -0
  54. ramses_tx/typing.py +181 -0
  55. ramses_tx/version.py +4 -0
  56. ramses_rf/discovery.py +0 -398
  57. ramses_rf/protocol/__init__.py +0 -59
  58. ramses_rf/protocol/backports.py +0 -42
  59. ramses_rf/protocol/command.py +0 -1561
  60. ramses_rf/protocol/const.py +0 -697
  61. ramses_rf/protocol/exceptions.py +0 -111
  62. ramses_rf/protocol/helpers.py +0 -390
  63. ramses_rf/protocol/opentherm.py +0 -1170
  64. ramses_rf/protocol/packet.py +0 -235
  65. ramses_rf/protocol/parsers.py +0 -2673
  66. ramses_rf/protocol/protocol.py +0 -613
  67. ramses_rf/protocol/transport.py +0 -1011
  68. ramses_rf/protocol/version.py +0 -10
  69. ramses_rf/system/hvac.py +0 -82
  70. ramses_rf-0.22.2.dist-info/METADATA +0 -64
  71. ramses_rf-0.22.2.dist-info/RECORD +0 -42
  72. ramses_rf-0.22.2.dist-info/top_level.txt +0 -1
@@ -1,235 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- #
4
- """RAMSES RF - a RAMSES-II protocol decoder & analyser.
5
-
6
- Decode/process a packet (packet that was received).
7
- """
8
- from __future__ import annotations
9
-
10
- import logging
11
- from datetime import datetime as dt
12
- from datetime import timedelta as td
13
-
14
- from .const import __dev_mode__
15
- from .exceptions import InvalidPacketError
16
- from .frame import Frame
17
- from .logger import getLogger
18
- from .opentherm import PARAMS_MSG_IDS, SCHEMA_MSG_IDS, STATUS_MSG_IDS
19
- from .ramses import CODES_SCHEMA, EXPIRES
20
-
21
- # skipcq: PY-W2000
22
- from .const import ( # noqa: F401, isort: skip, pylint: disable=unused-import
23
- I_,
24
- RP,
25
- RQ,
26
- W_,
27
- Code,
28
- )
29
-
30
-
31
- # these trade memory for speed
32
- _TD_SECONDS_000 = td(seconds=0)
33
- _TD_SECONDS_003 = td(seconds=3)
34
- _TD_SECONDS_360 = td(seconds=360)
35
- _TD_MINUTES_005 = td(minutes=5)
36
- _TD_MINUTES_060 = td(minutes=60)
37
-
38
-
39
- DEV_MODE = __dev_mode__ and False
40
-
41
- _LOGGER = logging.getLogger(__name__)
42
- if DEV_MODE:
43
- _LOGGER.setLevel(logging.DEBUG)
44
-
45
- _PKT_LOGGER = getLogger(f"{__name__}_log", pkt_log=True)
46
-
47
-
48
- def fraction_expired(age: td, age_limit: td) -> float:
49
- """Return the packet's age as fraction of its 'normal' lifetime."""
50
- return (age - _TD_SECONDS_003) / age_limit
51
-
52
-
53
- class Packet(Frame):
54
- """The Packet class (packets that were received).
55
-
56
- They have a datetime (when received) an RSSI, and other meta-fields.
57
- """
58
-
59
- _dtm: dt
60
- _rssi: str
61
-
62
- def __init__(self, gwy, dtm: dt, frame: str, **kwargs) -> None:
63
- """Create a packet from a string (actually from f"{RSSI} {frame}").
64
-
65
- Will raise InvalidPacketError if it is invalid.
66
- """
67
-
68
- super().__init__(frame[4:]) # remove RSSI
69
-
70
- self._gwy = gwy
71
- self._dtm: dt = dtm
72
-
73
- self._rssi: str = frame[0:3]
74
-
75
- self.comment: str = kwargs.get("comment", "")
76
- self.error_text: str = kwargs.get("err_msg", "")
77
- self.raw_frame: str = kwargs.get("raw_frame", "")
78
-
79
- self._timeout: None | bool | td = None # track pkt expiry
80
-
81
- # if DEV_MODE: # TODO: remove (is for testing only)
82
- # _ = self._has_array
83
- # _ = self._has_ctl
84
-
85
- self._validate(strict_checking=False)
86
-
87
- def _validate(self, *, strict_checking: bool = None) -> None:
88
- """Validate the packet, and parse the addresses if so (will log all packets).
89
-
90
- Raise an exception InvalidPacketError (InvalidAddrSetError) if it is not valid.
91
- """
92
-
93
- try:
94
- if self.error_text:
95
- raise InvalidPacketError(self.error_text)
96
-
97
- if not self._frame and self.comment: # log null pkts only if has a comment
98
- raise InvalidPacketError("Null packet")
99
-
100
- super()._validate(strict_checking=strict_checking) # no RSSI
101
-
102
- _PKT_LOGGER.info("", extra=self.__dict__) # the packet.log line
103
-
104
- except InvalidPacketError as exc: # incl. InvalidAddrSetError
105
- if self._frame or self.error_text:
106
- _PKT_LOGGER.warning("%s", exc, extra=self.__dict__)
107
- raise exc
108
-
109
- def __repr__(self) -> str:
110
- """Return an unambiguous string representation of this object."""
111
- try:
112
- hdr = f' # {self._hdr}{f" ({self._ctx})" if self._ctx else ""}'
113
- except (InvalidPacketError, NotImplementedError):
114
- hdr = ""
115
- try:
116
- dtm = self.dtm.isoformat(timespec="microseconds")
117
- except AttributeError:
118
- dtm = dt.min.isoformat(timespec="microseconds")
119
- return f"{dtm} ... {self}{hdr}"
120
-
121
- def __str__(self) -> str:
122
- """Return an brief readable string representation of this object."""
123
- return super().__repr__()
124
-
125
- @property
126
- def dtm(self) -> dt:
127
- return self._dtm
128
-
129
- def __eq__(self, other) -> bool:
130
- if not hasattr(other, "_frame"):
131
- return NotImplemented
132
- return self._frame[4:] == other._frame[4:]
133
-
134
- @staticmethod
135
- def _partition(pkt_line: str) -> tuple[str, str, str]: # map[str]
136
- """Partition a packet line into its three parts.
137
-
138
- Format: packet[ < parser-hint: ...][ * evofw3-err_msg][ # evofw3-comment]
139
- """
140
-
141
- fragment, _, comment = pkt_line.partition("#")
142
- fragment, _, err_msg = fragment.partition("*")
143
- pkt_str, _, _ = fragment.partition("<") # discard any parser hints
144
- return map(str.strip, (pkt_str, err_msg, comment)) # type: ignore[return-value]
145
-
146
- @property
147
- def _expired(self) -> bool | float:
148
- """Return the used fraction of the packet's 'normal' lifetime.
149
-
150
- A packet is 'expired' when >1.0 (should it be tombstoned when >2.0?). Returns
151
- False if the packet does not expire (e.g. a 10E0).
152
-
153
- NB: this is only the fact if the packet has expired, or not. Any opinion to if
154
- it *matters* that the packet has expired, is up to higher layers of the stack.
155
- """
156
-
157
- if self._timeout is None: # add 3s to account for timing drift
158
- self._timeout = pkt_timeout(self) or False
159
-
160
- if self._timeout is False:
161
- return False
162
-
163
- return fraction_expired(
164
- self._gwy._dt_now() - self.dtm, self._timeout # type: ignore[arg-type]
165
- )
166
-
167
- @classmethod
168
- def from_dict(cls, gwy, dtm: str, pkt_line: str):
169
- """Create a packet from a saved state (a curated dict)."""
170
- frame, _, comment = cls._partition(pkt_line)
171
- return cls(gwy, dt.fromisoformat(dtm), frame, comment=comment)
172
-
173
- @classmethod
174
- def from_file(cls, gwy, dtm: str, pkt_line: str):
175
- """Create a packet from a log file line."""
176
- frame, err_msg, comment = cls._partition(pkt_line)
177
- return cls(gwy, dt.fromisoformat(dtm), frame, err_msg=err_msg, comment=comment)
178
-
179
- @classmethod
180
- def from_port(cls, gwy, dtm: dt, pkt_line: str, raw_line: bytes = None):
181
- """Create a packet from a USB port (HGI80, evofw3)."""
182
- frame, err_msg, comment = cls._partition(pkt_line)
183
- return cls(
184
- gwy, dtm, frame, err_msg=err_msg, comment=comment, raw_frame=raw_line
185
- )
186
-
187
-
188
- def pkt_timeout(pkt: Packet) -> None | td: # NOTE: import OtbGateway ??
189
- """Return the pkt lifetime, or None if the packet does not expire (e.g. 10E0).
190
-
191
- Some codes require a valid payload to best determine lifetime (e.g. 1F09).
192
- """
193
-
194
- if pkt.verb in (RQ, W_):
195
- return _TD_SECONDS_000
196
-
197
- if pkt.code in (
198
- Code._0005,
199
- Code._000C,
200
- Code._0404,
201
- Code._10E0,
202
- ): # 0404 expired by 0006
203
- return None # TODO: exclude/remove devices caused by corrupt ADDRs?
204
-
205
- if pkt.code == Code._1FC9 and pkt.verb == RP:
206
- return None # TODO: check other verbs, they seem variable
207
-
208
- if pkt.code == Code._1F09: # sends I /sync_cycle
209
- # can't do better than 300s with reading the payload
210
- return _TD_SECONDS_360 if pkt.verb == I_ else _TD_SECONDS_000
211
-
212
- if pkt.code == Code._000A and pkt._has_array:
213
- return _TD_MINUTES_060 # sends I /1h
214
-
215
- if pkt.code in (Code._2309, Code._30C9) and pkt._has_array: # sends I /sync_cycle
216
- return _TD_SECONDS_360
217
-
218
- if pkt.code == Code._3220: # FIXME
219
- # if pkt.payload[4:6] in WRITE_MSG_IDS and Write-Data: # TODO
220
- # return _TD_SECONDS_003
221
- if pkt.payload[4:6] in SCHEMA_MSG_IDS:
222
- return None # SCHEMA_MSG_IDS[pkt.payload[4:6]]
223
- if pkt.payload[4:6] in PARAMS_MSG_IDS:
224
- return PARAMS_MSG_IDS[pkt.payload[4:6]]
225
- if pkt.payload[4:6] in STATUS_MSG_IDS:
226
- return STATUS_MSG_IDS[pkt.payload[4:6]]
227
- return _TD_MINUTES_005
228
-
229
- # if pkt.code in (Code._3B00, Code._3EF0, ): # TODO: 0008, 3EF0, 3EF1
230
- # return td(minutes=6.7) # TODO: WIP
231
-
232
- if (code := CODES_SCHEMA.get(pkt.code)) and EXPIRES in code:
233
- return CODES_SCHEMA[pkt.code][EXPIRES]
234
-
235
- return _TD_MINUTES_060