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.
Files changed (71) 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 +279 -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 +377 -513
  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.2.dist-info/METADATA +72 -0
  29. ramses_rf-0.51.2.dist-info/RECORD +55 -0
  30. {ramses_rf-0.22.40.dist-info → ramses_rf-0.51.2.dist-info}/WHEEL +1 -2
  31. ramses_rf-0.51.2.dist-info/entry_points.txt +2 -0
  32. {ramses_rf-0.22.40.dist-info → ramses_rf-0.51.2.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_rf/protocol → ramses_tx}/parsers.py +1266 -1003
  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 -1576
  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/protocol.py +0 -613
  66. ramses_rf/protocol/transport.py +0 -1011
  67. ramses_rf/protocol/version.py +0 -10
  68. ramses_rf/system/hvac.py +0 -82
  69. ramses_rf-0.22.40.dist-info/METADATA +0 -64
  70. ramses_rf-0.22.40.dist-info/RECORD +0 -42
  71. ramses_rf-0.22.40.dist-info/top_level.txt +0 -1
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- #
4
2
  """RAMSES RF - a RAMSES-II protocol decoder & analyser.
5
3
 
6
4
  This module wraps logger to provide bespoke functionality, especially for timestamps.
7
5
  """
6
+
8
7
  from __future__ import annotations
9
8
 
10
9
  import logging
@@ -12,15 +11,14 @@ import os
12
11
  import re
13
12
  import shutil
14
13
  import sys
14
+ from collections.abc import Callable, Mapping
15
15
  from datetime import datetime as dt
16
16
  from logging.handlers import TimedRotatingFileHandler as _TimedRotatingFileHandler
17
- from typing import Callable
17
+ from typing import Any
18
18
 
19
- from .const import __dev_mode__
20
- from .schemas import SZ_FILE_NAME, SZ_ROTATE_BACKUPS, SZ_ROTATE_BYTES
21
19
  from .version import VERSION
22
20
 
23
- DEV_MODE = __dev_mode__ and False
21
+ DEV_MODE = False
24
22
 
25
23
  _LOGGER = logging.getLogger(__name__)
26
24
  if DEV_MODE:
@@ -69,23 +67,23 @@ class _Logger(logging.Logger): # use pkt.dtm for the log record timestamp
69
67
 
70
68
  def makeRecord(
71
69
  self,
72
- name,
73
- level,
74
- fn,
75
- lno,
76
- msg,
77
- args,
78
- exc_info,
79
- func=None,
80
- extra=None,
81
- sinfo=None,
82
- ):
70
+ name: str,
71
+ level: int,
72
+ fn: str,
73
+ lno: int,
74
+ msg: object,
75
+ args: Any,
76
+ exc_info: Any,
77
+ func: str | None = None,
78
+ extra: Mapping[str, object] | None = None,
79
+ sinfo: str | None = None,
80
+ ) -> logging.LogRecord:
83
81
  """Create a specialized LogRecord with a bespoke timestamp.
84
82
 
85
83
  Will overwrite created and msecs (and thus asctime), but not relativeCreated.
86
84
  """
87
85
 
88
- extra = dict(extra) # work with a copy
86
+ extra = dict(extra or {}) # work with a copy
89
87
  extra["frame"] = extra.pop("_frame", "")
90
88
  if extra["frame"]:
91
89
  extra["frame"] = f" {extra['_rssi']} {extra['frame']}"
@@ -102,11 +100,11 @@ class _Logger(logging.Logger): # use pkt.dtm for the log record timestamp
102
100
  if rv.msg:
103
101
  rv.msg = f" < {rv.msg}"
104
102
 
105
- if getattr(rv, "error_text", None):
106
- rv.error_text = f" * {rv.error_text}"
103
+ if value := getattr(rv, "error_text", None): # HACK
104
+ rv.error_text = f" * {value}"
107
105
 
108
- if getattr(rv, "comment", None):
109
- rv.comment = f" # {rv.comment}"
106
+ if value := getattr(rv, "comment", None): # HACK
107
+ rv.comment = f" # {value}"
110
108
 
111
109
  return rv
112
110
 
@@ -118,7 +116,7 @@ class _Formatter: # format asctime with configurable precision
118
116
  default_time_format = "%Y-%m-%dT%H:%M:%S.%f"
119
117
  precision = 6
120
118
 
121
- def formatTime(self, record, datefmt=None) -> str:
119
+ def formatTime(self, record: logging.LogRecord, datefmt: str | None = None) -> str:
122
120
  """Return the creation time (asctime) of the LogRecord as formatted text.
123
121
 
124
122
  Allows for sub-millisecond precision, using datetime instead of time objects.
@@ -143,7 +141,7 @@ class Formatter(_Formatter, logging.Formatter): # type: ignore[misc]
143
141
  class PktLogFilter(logging.Filter): # record.levelno in (logging.INFO, logging.WARNING)
144
142
  """For packet log files, process only wanted packets."""
145
143
 
146
- def filter(self, record) -> bool:
144
+ def filter(self, record: logging.LogRecord) -> bool:
147
145
  """Return True if the record is to be processed."""
148
146
  # if record._frame[4:] or record.comment or record.error_text:
149
147
  return record.levelno in (logging.INFO, logging.WARNING)
@@ -152,21 +150,21 @@ class PktLogFilter(logging.Filter): # record.levelno in (logging.INFO, logging.
152
150
  class StdErrFilter(logging.Filter): # record.levelno >= logging.WARNING
153
151
  """For sys.stderr, process only wanted packets."""
154
152
 
155
- def filter(self, record) -> bool:
156
- """Return True if the record is to be processed."""
157
- return record.levelno >= logging.WARNING # WARNING-30, ERROR-40
153
+ def filter(self, record: logging.LogRecord) -> bool:
154
+ """Return True if the record is to be processed.""" # WARNING-30, ERROR-40
155
+ return record.levelno >= logging.WARNING
158
156
 
159
157
 
160
158
  class StdOutFilter(logging.Filter): # record.levelno < logging.WARNING
161
159
  """For sys.stdout, process only wanted packets."""
162
160
 
163
- def filter(self, record) -> bool:
164
- """Return True if the record is to be processed."""
165
- return record.levelno < logging.WARNING # INFO-20, DEBUG-10
161
+ def filter(self, record: logging.LogRecord) -> bool:
162
+ """Return True if the record is to be processed.""" # INFO-20, DEBUG-10
163
+ return record.levelno < logging.WARNING
166
164
 
167
165
 
168
166
  class TimedRotatingFileHandler(_TimedRotatingFileHandler):
169
- def __init__(self, *args, **kwargs) -> None:
167
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
170
168
  super().__init__(*args, **kwargs)
171
169
  assert self.when == "MIDNIGHT"
172
170
  self.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}$", re.ASCII)
@@ -176,10 +174,10 @@ class TimedRotatingFileHandler(_TimedRotatingFileHandler):
176
174
  # self.doRollover()
177
175
  # return super().emit(record)
178
176
 
179
- def getFilesToDelete(self): # zxdavb: my version
177
+ def getFilesToDelete(self) -> list[str]: # zxdavb: my version
180
178
  """Determine the files to delete when rolling over.
181
179
 
182
- Overriden as old log files not being deleted.
180
+ Overridden as old log files not being deleted.
183
181
  """
184
182
  # See bpo-44753 (this code is as was before that commit), bpo45628, bpo-46063
185
183
  dirName, baseName = os.path.split(self.baseFilename)
@@ -200,7 +198,9 @@ class TimedRotatingFileHandler(_TimedRotatingFileHandler):
200
198
  return result
201
199
 
202
200
 
203
- def getLogger(name=None, pkt_log=None): # permits a bespoke Logger class
201
+ def getLogger( # permits a bespoke Logger class
202
+ name: str | None = None, pkt_log: bool = False
203
+ ) -> logging.Logger:
204
204
  """Return a logger with the specified name, creating it if necessary.
205
205
 
206
206
  Used to set record timestamps to its packet timestamp instead of the current time.
@@ -208,25 +208,34 @@ def getLogger(name=None, pkt_log=None): # permits a bespoke Logger class
208
208
  if name is None or not pkt_log:
209
209
  return logging.getLogger(name)
210
210
 
211
- logging._acquireLock() # So no-one else uses our Logger class
211
+ # Acquire lock, so no-one else uses our Logger class
212
+ try: # TODO: remove this ASAP
213
+ logging._acquireLock() # type: ignore[attr-defined]
214
+ except AttributeError: # Python 3.13+
215
+ logging._lock.acquire() # type: ignore[attr-defined]
216
+
212
217
  klass = logging.getLoggerClass()
213
218
  logging.setLoggerClass(_Logger)
214
219
 
215
220
  logger = logging.getLogger(name)
216
221
 
217
222
  logging.setLoggerClass(klass)
218
- logging._releaseLock()
223
+
224
+ try: # TODO: remove this ASAP
225
+ logging._releaseLock() # type: ignore[attr-defined]
226
+ except AttributeError: # Python 3.13+
227
+ logging._lock.release() # type: ignore[attr-defined]
219
228
 
220
229
  return logger
221
230
 
222
231
 
223
- def set_logger_timesource(dtm_now: Callable):
232
+ def set_logger_timesource(dtm_now: Callable[..., dt]) -> None:
224
233
  """Set a custom record factory, with a bespoke source of timestamps.
225
234
 
226
235
  Used to have records with the same datetime as the most recent packet log record.
227
236
  """
228
237
 
229
- def record_factory(*args, **kwargs):
238
+ def record_factory(*args: Any, **kwargs: Any) -> logging.LogRecord:
230
239
  record = old_factory(*args, **kwargs)
231
240
 
232
241
  ct = dtm_now().timestamp()
@@ -240,12 +249,19 @@ def set_logger_timesource(dtm_now: Callable):
240
249
  logging.setLogRecordFactory(record_factory)
241
250
 
242
251
 
243
- def set_pkt_logging(logger, dt_now=None, cc_console: bool = False, **kwargs) -> None:
252
+ def set_pkt_logging(
253
+ logger: logging.Logger,
254
+ cc_console: bool = False,
255
+ file_name: str | None = None,
256
+ rotate_backups: int = 0,
257
+ rotate_bytes: int | None = None,
258
+ ) -> None:
244
259
  """Create/configure handlers, formatters, etc.
245
260
 
246
261
  Parameters:
247
- - backup_count: keep this many copies, and rotate at midnight unless:
248
- - max_bytes: rotate log files when log > rotate_size
262
+ - file_name: base of file to store packet logs in, from root
263
+ - rotate_backups: keep this many copies, and rotate at midnight unless:
264
+ - rotate_bytes: rotate log files when log > rotate_size
249
265
  """
250
266
 
251
267
  logger.propagate = False # log file is distinct from any app/debug logging
@@ -255,18 +271,15 @@ def set_pkt_logging(logger, dt_now=None, cc_console: bool = False, **kwargs) ->
255
271
  for handler in logger.handlers: # dont use logger.hasHandlers() as not propagating
256
272
  logger.removeHandler(handler)
257
273
 
258
- if file_name := kwargs.get(SZ_FILE_NAME):
259
- bkp_count = kwargs.get(SZ_ROTATE_BACKUPS, 0)
260
- max_bytes = kwargs.get(SZ_ROTATE_BYTES)
261
-
262
- if max_bytes:
263
- bkp_count = bkp_count or 2
274
+ if file_name: # note: this opens the packet_log file IO and may block
275
+ if rotate_bytes:
276
+ rotate_backups = rotate_backups or 2
264
277
  handler = logging.handlers.RotatingFileHandler(
265
- file_name, maxBytes=max_bytes, backupCount=bkp_count
278
+ file_name, maxBytes=rotate_bytes, backupCount=rotate_backups
266
279
  )
267
- elif bkp_count:
280
+ elif rotate_backups:
268
281
  handler = TimedRotatingFileHandler(
269
- file_name, when="MIDNIGHT", backupCount=bkp_count
282
+ file_name, when="MIDNIGHT", backupCount=rotate_backups
270
283
  )
271
284
  else:
272
285
  handler = logging.FileHandler(file_name)
@@ -285,7 +298,8 @@ def set_pkt_logging(logger, dt_now=None, cc_console: bool = False, **kwargs) ->
285
298
  logger.setLevel(logging.CRITICAL)
286
299
  return
287
300
 
288
- if cc_console:
301
+ if cc_console: # CC: output to stdout/stderr
302
+ console_fmt: ColoredFormatter | Formatter
289
303
  if _use_color_:
290
304
  console_fmt = ColoredFormatter(
291
305
  fmt=f"%(log_color)s{CONSOLE_FMT + COLOR_SUFFIX}",
@@ -310,6 +324,6 @@ def set_pkt_logging(logger, dt_now=None, cc_console: bool = False, **kwargs) ->
310
324
  extras = {
311
325
  "_frame": "",
312
326
  "error_text": "",
313
- "comment": f"ramses_rf {VERSION}",
327
+ "comment": f"ramses_tx {VERSION}",
314
328
  }
315
- logger.warning("", extra=extras)
329
+ logger.warning("", extra=extras) # initial log line