ramses-rf 0.52.4__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_cli/client.py +1 -0
- ramses_cli/debug.py +1 -1
- ramses_cli/utils/convert.py +2 -2
- ramses_rf/database.py +40 -17
- ramses_rf/device/base.py +14 -3
- ramses_rf/device/heat.py +1 -1
- ramses_rf/device/hvac.py +24 -21
- ramses_rf/entity_base.py +6 -6
- ramses_rf/gateway.py +21 -10
- ramses_rf/schemas.py +1 -1
- ramses_rf/system/zones.py +22 -2
- ramses_rf/version.py +1 -1
- {ramses_rf-0.52.4.dist-info → ramses_rf-0.52.5.dist-info}/METADATA +1 -1
- {ramses_rf-0.52.4.dist-info → ramses_rf-0.52.5.dist-info}/RECORD +26 -26
- {ramses_rf-0.52.4.dist-info → ramses_rf-0.52.5.dist-info}/WHEEL +1 -1
- ramses_tx/address.py +21 -6
- ramses_tx/command.py +18 -2
- ramses_tx/helpers.py +30 -10
- ramses_tx/message.py +11 -5
- ramses_tx/packet.py +13 -5
- ramses_tx/parsers.py +1039 -16
- ramses_tx/protocol.py +93 -18
- ramses_tx/transport.py +30 -6
- ramses_tx/version.py +1 -1
- {ramses_rf-0.52.4.dist-info → ramses_rf-0.52.5.dist-info}/entry_points.txt +0 -0
- {ramses_rf-0.52.4.dist-info → ramses_rf-0.52.5.dist-info}/licenses/LICENSE +0 -0
ramses_tx/helpers.py
CHANGED
|
@@ -131,28 +131,36 @@ file_time = _FILE_TIME()
|
|
|
131
131
|
def timestamp() -> float:
|
|
132
132
|
"""Return the number of seconds since the Unix epoch.
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
This function attempts to return a high-precision value, using specific
|
|
135
|
+
system calls on Windows if available.
|
|
136
|
+
:return: The current timestamp in seconds.
|
|
137
|
+
:rtype: float
|
|
135
138
|
"""
|
|
136
139
|
|
|
137
140
|
# see: https://www.python.org/dev/peps/pep-0564/
|
|
138
|
-
if sys.platform
|
|
141
|
+
if sys.platform == "win32":
|
|
142
|
+
# Windows uses a different epoch (1601-01-01)
|
|
143
|
+
ctypes.windll.kernel32.GetSystemTimePreciseAsFileTime(ctypes.byref(file_time))
|
|
144
|
+
_time = (file_time.dwLowDateTime + (file_time.dwHighDateTime << 32)) / 1e7
|
|
145
|
+
return float(_time - 134774 * 24 * 60 * 60)
|
|
146
|
+
else:
|
|
147
|
+
# Linux/macOS uses the Unix epoch (1970-01-01)
|
|
139
148
|
return time.time_ns() / 1e9
|
|
140
149
|
|
|
141
|
-
# otherwise, is since 1601-01-01T00:00:00Z
|
|
142
|
-
ctypes.windll.kernel32.GetSystemTimePreciseAsFileTime(ctypes.byref(file_time)) # type: ignore[unreachable]
|
|
143
|
-
_time = (file_time.dwLowDateTime + (file_time.dwHighDateTime << 32)) / 1e7
|
|
144
|
-
return _time - 134774 * 24 * 60 * 60
|
|
145
|
-
|
|
146
150
|
|
|
147
151
|
def dt_now() -> dt:
|
|
148
152
|
"""Return the current datetime as a local/naive datetime object.
|
|
149
153
|
|
|
150
154
|
This is slower, but potentially more accurate, than dt.now(), and is used mainly for
|
|
151
155
|
packet timestamps.
|
|
156
|
+
|
|
157
|
+
:return: The current local datetime.
|
|
158
|
+
:rtype: dt
|
|
152
159
|
"""
|
|
153
160
|
if sys.platform == "win32":
|
|
154
161
|
return dt.fromtimestamp(timestamp())
|
|
155
|
-
|
|
162
|
+
else:
|
|
163
|
+
return dt.now()
|
|
156
164
|
|
|
157
165
|
|
|
158
166
|
def dt_str() -> str:
|
|
@@ -369,7 +377,14 @@ def hex_from_str(value: str) -> str:
|
|
|
369
377
|
|
|
370
378
|
|
|
371
379
|
def hex_to_temp(value: HexStr4) -> bool | float | None: # TODO: remove bool
|
|
372
|
-
"""Convert a 2's complement
|
|
380
|
+
"""Convert a 4-byte 2's complement hex string to a float temperature ('C).
|
|
381
|
+
|
|
382
|
+
:param value: The 4-character hex string (e.g., '07D0')
|
|
383
|
+
:type value: HexStr4
|
|
384
|
+
:return: The temperature in Celsius, or None if N/A
|
|
385
|
+
:rtype: float | None
|
|
386
|
+
:raises ValueError: If input is not a 4-char hex string or temperature is invalid.
|
|
387
|
+
"""
|
|
373
388
|
if not isinstance(value, str) or len(value) != 4:
|
|
374
389
|
raise ValueError(f"Invalid value: {value}, is not a 4-char hex string")
|
|
375
390
|
if value == "31FF": # means: N/A (== 127.99, 2s complement), signed?
|
|
@@ -488,13 +503,18 @@ AIR_QUALITY_BASIS: dict[str, str] = {
|
|
|
488
503
|
|
|
489
504
|
# 31DA[2:6] and 12C8[2:6]
|
|
490
505
|
def parse_air_quality(value: HexStr4) -> PayDictT.AIR_QUALITY:
|
|
491
|
-
"""Return the air quality
|
|
506
|
+
"""Return the air quality percentage (0.0 to 1.0) and its basis.
|
|
492
507
|
|
|
493
508
|
The basis of the air quality level should be one of: VOC, CO2 or relative humidity.
|
|
494
509
|
If air_quality is EF, air_quality_basis should be 00.
|
|
495
510
|
|
|
496
511
|
The sensor value is None if there is no sensor present (is not an error).
|
|
497
512
|
The dict does not include the key if there is a sensor fault.
|
|
513
|
+
|
|
514
|
+
:param value: The 4-character hex string encoding quality and basis
|
|
515
|
+
:type value: HexStr4
|
|
516
|
+
:return: A dictionary containing the air quality and its basis (e.g., CO2, VOC)
|
|
517
|
+
:rtype: PayDictT.AIR_QUALITY
|
|
498
518
|
""" # VOC: Volatile organic compounds
|
|
499
519
|
|
|
500
520
|
# TODO: remove this as API used only internally...
|
ramses_tx/message.py
CHANGED
|
@@ -54,7 +54,9 @@ class MessageBase:
|
|
|
54
54
|
def __init__(self, pkt: Packet) -> None:
|
|
55
55
|
"""Create a message from a valid packet.
|
|
56
56
|
|
|
57
|
-
:
|
|
57
|
+
:param pkt: The packet to process into a message
|
|
58
|
+
:type pkt: Packet
|
|
59
|
+
:raises PacketInvalid: If the packet payload cannot be parsed.
|
|
58
60
|
"""
|
|
59
61
|
|
|
60
62
|
self._pkt = pkt
|
|
@@ -359,10 +361,14 @@ def re_compile_re_match(regex: str, string: str) -> bool: # Optional[Match[Any]
|
|
|
359
361
|
|
|
360
362
|
|
|
361
363
|
def _check_msg_payload(msg: MessageBase, payload: str) -> None:
|
|
362
|
-
"""Validate
|
|
363
|
-
|
|
364
|
-
:
|
|
365
|
-
|
|
364
|
+
"""Validate a packet's payload against its verb/code pair.
|
|
365
|
+
|
|
366
|
+
:param msg: The message object being validated
|
|
367
|
+
:type msg: MessageBase
|
|
368
|
+
:param payload: The raw hex payload string
|
|
369
|
+
:type payload: str
|
|
370
|
+
:raises PacketInvalid: If the code is unknown or verb/code pair is invalid.
|
|
371
|
+
:raises PacketPayloadInvalid: If the payload does not match the expected regex.
|
|
366
372
|
"""
|
|
367
373
|
|
|
368
374
|
_ = repr(msg._pkt) # HACK: ? raise InvalidPayloadError
|
ramses_tx/packet.py
CHANGED
|
@@ -48,9 +48,14 @@ class Packet(Frame):
|
|
|
48
48
|
_rssi: str
|
|
49
49
|
|
|
50
50
|
def __init__(self, dtm: dt, frame: str, **kwargs: Any) -> None:
|
|
51
|
-
"""Create a packet from a
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
"""Create a packet from a raw frame string.
|
|
52
|
+
|
|
53
|
+
:param dtm: The timestamp when the packet was received
|
|
54
|
+
:type dtm: dt
|
|
55
|
+
:param frame: The raw frame string, typically including RSSI
|
|
56
|
+
:type frame: str
|
|
57
|
+
:param kwargs: Metadata including 'comment', 'err_msg', or 'raw_frame'
|
|
58
|
+
:raises PacketInvalid: If the frame content is malformed.
|
|
54
59
|
"""
|
|
55
60
|
|
|
56
61
|
super().__init__(frame[4:]) # remove RSSI
|
|
@@ -156,9 +161,12 @@ class Packet(Frame):
|
|
|
156
161
|
|
|
157
162
|
# TODO: remove None as a possible return value
|
|
158
163
|
def pkt_lifespan(pkt: Packet) -> td: # import OtbGateway??
|
|
159
|
-
"""Return the
|
|
164
|
+
"""Return the lifespan of a packet before it expires.
|
|
160
165
|
|
|
161
|
-
|
|
166
|
+
:param pkt: The packet instance to evaluate
|
|
167
|
+
:type pkt: Packet
|
|
168
|
+
:return: The duration the packet's data remains valid
|
|
169
|
+
:rtype: td
|
|
162
170
|
"""
|
|
163
171
|
|
|
164
172
|
if pkt.verb in (RQ, W_):
|