ramses-rf 0.22.40__py3-none-any.whl → 0.51.2__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 +18 -0
- ramses_cli/client.py +597 -0
- ramses_cli/debug.py +20 -0
- ramses_cli/discovery.py +405 -0
- ramses_cli/utils/cat_slow.py +17 -0
- ramses_cli/utils/convert.py +60 -0
- ramses_rf/__init__.py +31 -10
- ramses_rf/binding_fsm.py +787 -0
- ramses_rf/const.py +124 -105
- ramses_rf/database.py +297 -0
- ramses_rf/device/__init__.py +69 -39
- ramses_rf/device/base.py +187 -376
- ramses_rf/device/heat.py +540 -552
- ramses_rf/device/hvac.py +279 -171
- ramses_rf/dispatcher.py +153 -177
- ramses_rf/entity_base.py +478 -361
- ramses_rf/exceptions.py +82 -0
- ramses_rf/gateway.py +377 -513
- ramses_rf/helpers.py +57 -19
- ramses_rf/py.typed +0 -0
- ramses_rf/schemas.py +148 -194
- ramses_rf/system/__init__.py +16 -23
- ramses_rf/system/faultlog.py +363 -0
- ramses_rf/system/heat.py +295 -302
- ramses_rf/system/schedule.py +312 -198
- ramses_rf/system/zones.py +318 -238
- ramses_rf/version.py +2 -8
- ramses_rf-0.51.2.dist-info/METADATA +72 -0
- ramses_rf-0.51.2.dist-info/RECORD +55 -0
- {ramses_rf-0.22.40.dist-info → ramses_rf-0.51.2.dist-info}/WHEEL +1 -2
- ramses_rf-0.51.2.dist-info/entry_points.txt +2 -0
- {ramses_rf-0.22.40.dist-info → ramses_rf-0.51.2.dist-info/licenses}/LICENSE +1 -1
- ramses_tx/__init__.py +160 -0
- {ramses_rf/protocol → ramses_tx}/address.py +65 -59
- ramses_tx/command.py +1454 -0
- ramses_tx/const.py +903 -0
- ramses_tx/exceptions.py +92 -0
- {ramses_rf/protocol → ramses_tx}/fingerprints.py +56 -15
- {ramses_rf/protocol → ramses_tx}/frame.py +132 -131
- ramses_tx/gateway.py +338 -0
- ramses_tx/helpers.py +883 -0
- {ramses_rf/protocol → ramses_tx}/logger.py +67 -53
- {ramses_rf/protocol → ramses_tx}/message.py +155 -191
- ramses_tx/opentherm.py +1260 -0
- ramses_tx/packet.py +210 -0
- {ramses_rf/protocol → ramses_tx}/parsers.py +1266 -1003
- ramses_tx/protocol.py +801 -0
- ramses_tx/protocol_fsm.py +672 -0
- ramses_tx/py.typed +0 -0
- {ramses_rf/protocol → ramses_tx}/ramses.py +262 -185
- {ramses_rf/protocol → ramses_tx}/schemas.py +150 -133
- ramses_tx/transport.py +1471 -0
- ramses_tx/typed_dicts.py +492 -0
- ramses_tx/typing.py +181 -0
- ramses_tx/version.py +4 -0
- ramses_rf/discovery.py +0 -398
- ramses_rf/protocol/__init__.py +0 -59
- ramses_rf/protocol/backports.py +0 -42
- ramses_rf/protocol/command.py +0 -1576
- ramses_rf/protocol/const.py +0 -697
- ramses_rf/protocol/exceptions.py +0 -111
- ramses_rf/protocol/helpers.py +0 -390
- ramses_rf/protocol/opentherm.py +0 -1170
- ramses_rf/protocol/packet.py +0 -235
- ramses_rf/protocol/protocol.py +0 -613
- ramses_rf/protocol/transport.py +0 -1011
- ramses_rf/protocol/version.py +0 -10
- ramses_rf/system/hvac.py +0 -82
- ramses_rf-0.22.40.dist-info/METADATA +0 -64
- ramses_rf-0.22.40.dist-info/RECORD +0 -42
- ramses_rf-0.22.40.dist-info/top_level.txt +0 -1
ramses_rf/protocol/packet.py
DELETED
|
@@ -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
|