ramses-rf 0.52.0__py3-none-any.whl → 0.52.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_rf/__init__.py +1 -0
- ramses_rf/database.py +62 -44
- ramses_rf/device/heat.py +1 -0
- ramses_rf/device/hvac.py +118 -58
- ramses_rf/dispatcher.py +9 -4
- ramses_rf/entity_base.py +77 -65
- ramses_rf/gateway.py +3 -2
- ramses_rf/schemas.py +1 -1
- ramses_rf/version.py +1 -1
- {ramses_rf-0.52.0.dist-info → ramses_rf-0.52.2.dist-info}/METADATA +1 -1
- {ramses_rf-0.52.0.dist-info → ramses_rf-0.52.2.dist-info}/RECORD +21 -21
- ramses_tx/__init__.py +3 -1
- ramses_tx/command.py +6 -5
- ramses_tx/gateway.py +1 -1
- ramses_tx/parsers.py +41 -37
- ramses_tx/schemas.py +24 -15
- ramses_tx/transport.py +31 -22
- ramses_tx/version.py +1 -1
- {ramses_rf-0.52.0.dist-info → ramses_rf-0.52.2.dist-info}/WHEEL +0 -0
- {ramses_rf-0.52.0.dist-info → ramses_rf-0.52.2.dist-info}/entry_points.txt +0 -0
- {ramses_rf-0.52.0.dist-info → ramses_rf-0.52.2.dist-info}/licenses/LICENSE +0 -0
ramses_rf/__init__.py
CHANGED
ramses_rf/database.py
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
2
|
+
"""
|
|
3
|
+
RAMSES RF - Message database and index.
|
|
4
|
+
|
|
5
|
+
.. table:: Database Query Methods[^1][#fn1]
|
|
6
|
+
:widths: auto
|
|
7
|
+
|
|
8
|
+
===== ============ =========== ========== ==== ========================
|
|
9
|
+
ix method name args returns uses used by
|
|
10
|
+
===== ============ =========== ========== ==== ========================
|
|
11
|
+
i1 get Msg, kwargs tuple(Msg) i3
|
|
12
|
+
i2 contains kwargs bool i4
|
|
13
|
+
i3 _select_from kwargs tuple(Msg) i4
|
|
14
|
+
i4 qry_dtms kwargs list(dtm)
|
|
15
|
+
i5 qry sql, kwargs tuple(Msg) _msgs()
|
|
16
|
+
i6 qry_field sql, kwargs tuple(fld) e4, e5
|
|
17
|
+
i7 get_rp_codes src, dst list(Code) Discovery-supported_cmds
|
|
18
|
+
===== ============ =========== ========== ==== ========================
|
|
19
|
+
|
|
20
|
+
[#fn1] A word of explanation.
|
|
21
|
+
[^1]: ex = entity_base.py query methods
|
|
22
|
+
"""
|
|
3
23
|
|
|
4
24
|
from __future__ import annotations
|
|
5
25
|
|
|
@@ -62,9 +82,10 @@ def payload_keys(parsed_payload: list[dict] | dict) -> str: # type: ignore[type
|
|
|
62
82
|
|
|
63
83
|
|
|
64
84
|
class MessageIndex:
|
|
65
|
-
"""A
|
|
66
|
-
Index holds the latest
|
|
67
|
-
(
|
|
85
|
+
"""A central in-memory SQLite3 database for indexing RF messages.
|
|
86
|
+
Index holds all the latest messages to & from all devices by `dtm`
|
|
87
|
+
(timestamp) and `hdr` header
|
|
88
|
+
(example of a hdr: ``000C|RP|01:223036|0208``)."""
|
|
68
89
|
|
|
69
90
|
_housekeeping_task: asyncio.Task[None]
|
|
70
91
|
|
|
@@ -72,7 +93,8 @@ class MessageIndex:
|
|
|
72
93
|
"""Instantiate a message database/index."""
|
|
73
94
|
|
|
74
95
|
self.maintain = maintain
|
|
75
|
-
self._msgs: MsgDdT = OrderedDict() # stores all messages for retrieval.
|
|
96
|
+
self._msgs: MsgDdT = OrderedDict() # stores all messages for retrieval.
|
|
97
|
+
# Filled & cleaned up in housekeeping_loop.
|
|
76
98
|
|
|
77
99
|
# Connect to a SQLite DB in memory
|
|
78
100
|
self._cx = sqlite3.connect(
|
|
@@ -122,16 +144,17 @@ class MessageIndex:
|
|
|
122
144
|
def _setup_db_schema(self) -> None:
|
|
123
145
|
"""Set up the message database schema.
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
147
|
+
.. note::
|
|
148
|
+
messages TABLE Fields:
|
|
149
|
+
|
|
150
|
+
- dtm message timestamp
|
|
151
|
+
- verb " I", "RQ" etc.
|
|
152
|
+
- src message origin address
|
|
153
|
+
- dst message destination address
|
|
154
|
+
- code packet code aka command class e.g. _0005, _31DA
|
|
155
|
+
- ctx message context, created from payload as index + extra markers (Heat)
|
|
156
|
+
- hdr packet header e.g. 000C|RP|01:223036|0208 (see: src/ramses_tx/frame.py)
|
|
157
|
+
- plk the keys stored in the parsed payload, separated by the | char
|
|
135
158
|
"""
|
|
136
159
|
|
|
137
160
|
self._cu.execute(
|
|
@@ -160,7 +183,7 @@ class MessageIndex:
|
|
|
160
183
|
|
|
161
184
|
async def _housekeeping_loop(self) -> None:
|
|
162
185
|
"""Periodically remove stale messages from the index,
|
|
163
|
-
unless self.maintain is False."""
|
|
186
|
+
unless `self.maintain` is False."""
|
|
164
187
|
|
|
165
188
|
async def housekeeping(dt_now: dt, _cutoff: td = td(days=1)) -> None:
|
|
166
189
|
"""
|
|
@@ -196,6 +219,7 @@ class MessageIndex:
|
|
|
196
219
|
"""
|
|
197
220
|
Add a single message to the MessageIndex.
|
|
198
221
|
Logs a warning if there is a duplicate dtm.
|
|
222
|
+
|
|
199
223
|
:returns: any message that was removed because it had the same header
|
|
200
224
|
"""
|
|
201
225
|
# TODO: eventually, may be better to use SqlAlchemy
|
|
@@ -224,22 +248,26 @@ class MessageIndex:
|
|
|
224
248
|
pass # self._lock.release()
|
|
225
249
|
|
|
226
250
|
if (
|
|
227
|
-
dup and msg.src is not msg.
|
|
228
|
-
): # when src==dst, expect to add duplicate, don't
|
|
229
|
-
_LOGGER.
|
|
251
|
+
dup and msg.src is not msg.dst and not msg.dst.id.startswith("18:") # HGI
|
|
252
|
+
): # when src==dst, expect to add duplicate, don't warn
|
|
253
|
+
_LOGGER.debug(
|
|
230
254
|
"Overwrote dtm (%s) for %s: %s (contrived log?)",
|
|
231
255
|
msg.dtm,
|
|
232
256
|
msg._pkt._hdr,
|
|
233
257
|
dup[0]._pkt,
|
|
234
258
|
)
|
|
235
259
|
if old is not None:
|
|
236
|
-
_LOGGER.
|
|
260
|
+
_LOGGER.debug("Old msg replaced: %s", old)
|
|
237
261
|
|
|
238
262
|
return old
|
|
239
263
|
|
|
240
264
|
def add_record(self, src: str, code: str = "", verb: str = "") -> None:
|
|
241
265
|
"""
|
|
242
|
-
Add a single record to the MessageIndex with timestamp now() and no Message contents.
|
|
266
|
+
Add a single record to the MessageIndex with timestamp `now()` and no Message contents.
|
|
267
|
+
|
|
268
|
+
:param src: device id to use as source address
|
|
269
|
+
:param code: device id to use as destination address (can be identical)
|
|
270
|
+
:param verb: two letter verb str to use
|
|
243
271
|
"""
|
|
244
272
|
# Used by OtbGateway init, via entity_base.py
|
|
245
273
|
dtm: DtmStrT = dt.strftime(dt.now(), "%Y-%m-%dT%H:%M:%S") # type: ignore[assignment]
|
|
@@ -274,6 +302,7 @@ class MessageIndex:
|
|
|
274
302
|
def _insert_into(self, msg: Message) -> Message | None:
|
|
275
303
|
"""
|
|
276
304
|
Insert a message into the index.
|
|
305
|
+
|
|
277
306
|
:returns: any message replaced (by same hdr)
|
|
278
307
|
"""
|
|
279
308
|
assert msg._pkt._hdr is not None, "Skipping: Packet has no hdr: {msg._pkt}"
|
|
@@ -305,7 +334,7 @@ class MessageIndex:
|
|
|
305
334
|
payload_keys(msg.payload),
|
|
306
335
|
),
|
|
307
336
|
)
|
|
308
|
-
_LOGGER.
|
|
337
|
+
_LOGGER.debug(f"Added {msg} to gwy.msg_db")
|
|
309
338
|
|
|
310
339
|
return _old_msgs[0] if _old_msgs else None
|
|
311
340
|
|
|
@@ -348,6 +377,7 @@ class MessageIndex:
|
|
|
348
377
|
|
|
349
378
|
def _delete_from(self, **kwargs: bool | dt | str) -> tuple[Message, ...]:
|
|
350
379
|
"""Remove message(s) from the index.
|
|
380
|
+
|
|
351
381
|
:returns: any messages that were removed"""
|
|
352
382
|
|
|
353
383
|
msgs = self._select_from(**kwargs)
|
|
@@ -359,32 +389,14 @@ class MessageIndex:
|
|
|
359
389
|
|
|
360
390
|
return msgs
|
|
361
391
|
|
|
362
|
-
# MessageIndex msg_db query methods
|
|
363
|
-
# (ex = entity_base.py query methods
|
|
364
|
-
#
|
|
365
|
-
# +----+--------------+-------------+------------+------+--------------------------+
|
|
366
|
-
# | ix |method name | args | returns | uses | used by |
|
|
367
|
-
# +====+==============+=============+============+======+==========================+
|
|
368
|
-
# | i1 | get | Msg/kwargs | tuple[Msg] | i3 | |
|
|
369
|
-
# +----+--------------+-------------+------------+------+--------------------------+
|
|
370
|
-
# | i2 | contains | kwargs | bool | i4 | |
|
|
371
|
-
# +----+--------------+-------------+------------+------+--------------------------+
|
|
372
|
-
# | i3 | _select_from | kwargs | tuple[Msg] | i4 | |
|
|
373
|
-
# +----+--------------+-------------+------------+------+--------------------------+
|
|
374
|
-
# | i4 | qry_dtms | kwargs | list(dtm) | | |
|
|
375
|
-
# +----+--------------+-------------+------------+------+--------------------------+
|
|
376
|
-
# | i5 | qry | sql, kwargs | tuple[Msg] | | _msgs() |
|
|
377
|
-
# +----+--------------+-------------+------------+------+--------------------------+
|
|
378
|
-
# | i6 | qry_field | sql, kwargs | tuple[fld] | | e4, e5 |
|
|
379
|
-
# +----+--------------+-------------+------------+------+--------------------------+
|
|
380
|
-
# | i7 | get_rp_codes | src, dst | list[Code] | | Discovery#supported_cmds |
|
|
381
|
-
# +----+--------------+-------------+------------+------+--------------------------+
|
|
392
|
+
# MessageIndex msg_db query methods
|
|
382
393
|
|
|
383
394
|
def get(
|
|
384
395
|
self, msg: Message | None = None, **kwargs: bool | dt | str
|
|
385
396
|
) -> tuple[Message, ...]:
|
|
386
397
|
"""
|
|
387
398
|
Public method to get a set of message(s) from the index.
|
|
399
|
+
|
|
388
400
|
:param msg: Message to return, by dtm (expect a single result as dtm is unique key)
|
|
389
401
|
:param kwargs: data table field names and criteria, e.g. (hdr=...)
|
|
390
402
|
:return: tuple of matching Messages
|
|
@@ -401,6 +413,7 @@ class MessageIndex:
|
|
|
401
413
|
def contains(self, **kwargs: bool | dt | str) -> bool:
|
|
402
414
|
"""
|
|
403
415
|
Check if the MessageIndex contains at least 1 record that matches the provided fields.
|
|
416
|
+
|
|
404
417
|
:param kwargs: (exact) SQLite table field_name: required_value pairs
|
|
405
418
|
:return: True if at least one message fitting the given conditions is present, False when qry returned empty
|
|
406
419
|
"""
|
|
@@ -410,6 +423,7 @@ class MessageIndex:
|
|
|
410
423
|
def _select_from(self, **kwargs: bool | dt | str) -> tuple[Message, ...]:
|
|
411
424
|
"""
|
|
412
425
|
Select message(s) using the MessageIndex.
|
|
426
|
+
|
|
413
427
|
:param kwargs: (exact) SQLite table field_name: required_value pairs
|
|
414
428
|
:returns: a tuple of qualifying messages
|
|
415
429
|
"""
|
|
@@ -422,6 +436,7 @@ class MessageIndex:
|
|
|
422
436
|
def qry_dtms(self, **kwargs: bool | dt | str) -> list[Any]:
|
|
423
437
|
"""
|
|
424
438
|
Select from the ImageIndex a list of dtms that match the provided arguments.
|
|
439
|
+
|
|
425
440
|
:param kwargs: data table field names and criteria
|
|
426
441
|
:return: list of unformatted dtms that match, useful for msg lookup, or an empty list if 0 matches
|
|
427
442
|
"""
|
|
@@ -444,6 +459,7 @@ class MessageIndex:
|
|
|
444
459
|
def qry(self, sql: str, parameters: tuple[str, ...]) -> tuple[Message, ...]:
|
|
445
460
|
"""
|
|
446
461
|
Get a tuple of messages from _msgs using the index, given sql and parameters.
|
|
462
|
+
|
|
447
463
|
:param sql: a bespoke SQL query SELECT string that should return dtm as first field
|
|
448
464
|
:param parameters: tuple of kwargs with the selection filter
|
|
449
465
|
:return: a tuple of qualifying messages
|
|
@@ -468,12 +484,13 @@ class MessageIndex:
|
|
|
468
484
|
if ts in self._msgs:
|
|
469
485
|
lst.append(self._msgs[ts])
|
|
470
486
|
else: # happens in tests with artificial msg from heat
|
|
471
|
-
_LOGGER.
|
|
487
|
+
_LOGGER.info("MessageIndex timestamp %s not in device messages", ts)
|
|
472
488
|
return tuple(lst)
|
|
473
489
|
|
|
474
490
|
def get_rp_codes(self, parameters: tuple[str, ...]) -> list[Code]:
|
|
475
491
|
"""
|
|
476
492
|
Get a list of Codes from the index, given parameters.
|
|
493
|
+
|
|
477
494
|
:param parameters: tuple of additional kwargs
|
|
478
495
|
:return: list of Code: value pairs
|
|
479
496
|
"""
|
|
@@ -499,6 +516,7 @@ class MessageIndex:
|
|
|
499
516
|
) -> list[tuple[dt | str, str]]:
|
|
500
517
|
"""
|
|
501
518
|
Get a list of fields from the index, given select sql and parameters.
|
|
519
|
+
|
|
502
520
|
:param sql: a bespoke SQL query SELECT string
|
|
503
521
|
:param parameters: tuple of additional kwargs
|
|
504
522
|
:return: list of key: value pairs as defined in sql
|
|
@@ -529,7 +547,7 @@ class MessageIndex:
|
|
|
529
547
|
# if include_expired or not self._msgs[ts].HAS_EXPIRED: # not working
|
|
530
548
|
lst.append(self._msgs[ts])
|
|
531
549
|
else: # happens in tests with dummy msg from heat init
|
|
532
|
-
_LOGGER.
|
|
550
|
+
_LOGGER.info("MessageIndex ts %s not in device messages", ts)
|
|
533
551
|
return tuple(lst)
|
|
534
552
|
|
|
535
553
|
def clr(self) -> None:
|
ramses_rf/device/heat.py
CHANGED
|
@@ -1263,6 +1263,7 @@ class BdrSwitch(Actuator, RelayDemand): # BDR (13):
|
|
|
1263
1263
|
"""The BDR class, such as a BDR91.
|
|
1264
1264
|
|
|
1265
1265
|
BDR91s can be used in six distinct modes, including:
|
|
1266
|
+
|
|
1266
1267
|
- x2 boiler controller (FC/TPI): either traditional, or newer heat pump-aware
|
|
1267
1268
|
- x1 electric heat zones (0x/ELE)
|
|
1268
1269
|
- x1 zone valve zones (0x/VAL)
|
ramses_rf/device/hvac.py
CHANGED
|
@@ -728,12 +728,15 @@ class HvacVentilator(FilterChange): # FAN: RP/31DA, I/31D[9A], 2411
|
|
|
728
728
|
In HomeAssistant, ramses_cc, you can set a bound device in the device configuration.
|
|
729
729
|
|
|
730
730
|
System schema and known devices example:
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
731
|
+
|
|
732
|
+
.. code-block::
|
|
733
|
+
|
|
734
|
+
"32:153289":
|
|
735
|
+
bound: "37:168270"
|
|
736
|
+
class: FAN
|
|
737
|
+
"37:168270":
|
|
738
|
+
class: REM
|
|
739
|
+
faked: true
|
|
737
740
|
|
|
738
741
|
:param device_id: The unique identifier of the device to bind
|
|
739
742
|
:type device_id: str
|
|
@@ -927,7 +930,7 @@ class HvacVentilator(FilterChange): # FAN: RP/31DA, I/31D[9A], 2411
|
|
|
927
930
|
"""
|
|
928
931
|
res_mode: list = self._msg_qry(sql)
|
|
929
932
|
# SQLite query on MessageIndex
|
|
930
|
-
_LOGGER.
|
|
933
|
+
_LOGGER.debug(f"{res_mode} # FAN_MODE FETCHED from MessageIndex")
|
|
931
934
|
|
|
932
935
|
sql = f"""
|
|
933
936
|
SELECT code from messages WHERE verb in (' I', 'RP')
|
|
@@ -936,7 +939,7 @@ class HvacVentilator(FilterChange): # FAN: RP/31DA, I/31D[9A], 2411
|
|
|
936
939
|
"""
|
|
937
940
|
res_rate: list = self._msg_qry(sql)
|
|
938
941
|
# SQLite query on MessageIndex
|
|
939
|
-
_LOGGER.
|
|
942
|
+
_LOGGER.debug(
|
|
940
943
|
f"{res_rate} # FAN_RATE FETCHED from MessageIndex"
|
|
941
944
|
) # DEBUG always empty?
|
|
942
945
|
|
|
@@ -1142,7 +1145,7 @@ def class_dev_hvac(
|
|
|
1142
1145
|
) -> type[DeviceHvac]:
|
|
1143
1146
|
"""Return a device class, but only if the device must be from the HVAC group.
|
|
1144
1147
|
|
|
1145
|
-
May return a base
|
|
1148
|
+
May return a base class, `DeviceHvac`, which will need promotion.
|
|
1146
1149
|
"""
|
|
1147
1150
|
|
|
1148
1151
|
if not eavesdrop:
|
|
@@ -1206,54 +1209,111 @@ _REMOTES = {
|
|
|
1206
1209
|
# see: https://github.com/arjenhiemstra/ithowifi/blob/master/software/NRG_itho_wifi/src/IthoPacket.h
|
|
1207
1210
|
|
|
1208
1211
|
"""
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
"
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
"
|
|
1224
|
-
"
|
|
1225
|
-
"
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
"
|
|
1246
|
-
"
|
|
1247
|
-
"
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
"
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1212
|
+
Itho Remote (model) enums.
|
|
1213
|
+
|
|
1214
|
+
CVE/HRU remote (536-0124) RFT W: 3 modes, timer
|
|
1215
|
+
-------------------------------------------------
|
|
1216
|
+
|
|
1217
|
+
.. table:: 536-0124
|
|
1218
|
+
:widths: auto
|
|
1219
|
+
|
|
1220
|
+
=========== ========================= ================================================
|
|
1221
|
+
"away": (Code._22F1, 00, 01|04"), how to invoke?
|
|
1222
|
+
"low": (Code._22F1, 00, 02|04"), aka eco
|
|
1223
|
+
"medium": (Code._22F1, 00, 03|04"), aka auto (with sensors) - is that only for 63?
|
|
1224
|
+
"high": (Code._22F1, 00, 04|04"), aka full
|
|
1225
|
+
|
|
1226
|
+
"timer_1": (Code._22F3, 00, 00|0A"), 10 minutes full speed
|
|
1227
|
+
"timer_2": (Code._22F3, 00, 00|14"), 20 minutes full speed
|
|
1228
|
+
"timer_3": (Code._22F3, 00, 00|1E"), 30 minutes full speed
|
|
1229
|
+
=========== ========================= ================================================
|
|
1230
|
+
|
|
1231
|
+
RFT-AUTO (536-0150) RFT CAR: 2 modes, auto, timer: idx = 63, essentially same as above, but also...
|
|
1232
|
+
-----------------------------------------------------------------------------------------------------
|
|
1233
|
+
|
|
1234
|
+
.. table:: 536-0150
|
|
1235
|
+
:widths: auto
|
|
1236
|
+
|
|
1237
|
+
============= ========================= ================================================
|
|
1238
|
+
"auto_night": (Code._22F8, 63, 02|03"), additional - press auto x2
|
|
1239
|
+
============= ========================= ================================================
|
|
1240
|
+
|
|
1241
|
+
RFT-RV (04-00046), RFT-CO2 (04-00045) - sensors with control
|
|
1242
|
+
------------------------------------------------------------
|
|
1243
|
+
|
|
1244
|
+
.. table:: 04-00046
|
|
1245
|
+
:widths: auto
|
|
1246
|
+
|
|
1247
|
+
============== ======================================== =============
|
|
1248
|
+
"medium": (Code._22F1, 00, 03|07"), 1=away, 2=low?
|
|
1249
|
+
"auto": (Code._22F1, 00, 05|07"), 4=high
|
|
1250
|
+
"auto_night": (Code._22F1, 00, 0B|0B"),
|
|
1251
|
+
|
|
1252
|
+
"timer_1": (Code._22F3, 00, 00|0A, 00|00, 0000"), 10 minutes
|
|
1253
|
+
"timer_2": (Code._22F3, 00, 00|14, 00|00, 0000"), 20 minutes
|
|
1254
|
+
"timer_3": (Code._22F3, 00, 00|1E, 00|00, 0000"), 30 minutes
|
|
1255
|
+
============== ======================================== =============
|
|
1256
|
+
|
|
1257
|
+
RFT-PIR (545-7550) - presence sensor
|
|
1258
|
+
------------------------------------
|
|
1259
|
+
|
|
1260
|
+
RFT_DF: DemandFlow remote (536-0146)
|
|
1261
|
+
------------------------------------
|
|
1262
|
+
|
|
1263
|
+
.. table:: 536-0146
|
|
1264
|
+
:widths: auto
|
|
1265
|
+
|
|
1266
|
+
=========== ================================ =========================================
|
|
1267
|
+
"timer_1": (Code._22F3, 00, 42|03, 03|03"), 0b01-000-010 = 3 hrs, back to last mode
|
|
1268
|
+
"timer_2": (Code._22F3, 00, 42|06, 03|03"), 0b01-000-010 = 6 hrs, back to last mode
|
|
1269
|
+
"timer_3": (Code._22F3, 00, 42|09, 03|03"), 0b01-000-010 = 9 hrs, back to last mode
|
|
1270
|
+
"cook_30": (Code._22F3, 00, 02|1E, 02|03"), 30 mins (press 1x)
|
|
1271
|
+
"cook_60": (Code._22F3, 00, 02|3C, 02|03"), 60 mins (press 2x)
|
|
1272
|
+
|
|
1273
|
+
"low": (Code._22F8, 00, 01|02"), ?eco co2 <= 1200 ppm?
|
|
1274
|
+
"high": (Code._22F8, 00, 02|02"), ?comfort co2 <= 1000 ppm?
|
|
1275
|
+
=========== ================================ =========================================
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
Join commands:
|
|
1279
|
+
--------------
|
|
1280
|
+
|
|
1281
|
+
.. table:: join per accessory type
|
|
1282
|
+
:widths: auto
|
|
1283
|
+
|
|
1284
|
+
========== ================= ===================== ========================= ========================== ========================= ========================== ================= ==========
|
|
1285
|
+
type set 1 set 2 set 3 set 4 set 5 set 6 description art #
|
|
1286
|
+
========== ================= ===================== ========================= ========================== ========================= ========================== ================= ==========
|
|
1287
|
+
"CVERFT": (Code._1FC9, 00, Code._22F1, 0x000000, 01, Code._10E0, 0x000000") CVE/HRU remote (536-0124)
|
|
1288
|
+
"AUTORFT": (Code._1FC9, 63, Code._22F8, 0x000000, 01, Code._10E0, 0x000000") AUTO RFT (536-0150)
|
|
1289
|
+
"DF": (Code._1FC9, 00, Code._22F8, 0x000000, 00, Code._10E0, 0x000000") DemandFlow remote (536-0146)
|
|
1290
|
+
"RV": (Code._1FC9, 00, Code._12A0, 0x000000, 01, Code._10E0, 0x000000, 00, Code._31E0, 0x000000, 00, Code._1FC9, 0x000000") RFT-RV (04-00046)
|
|
1291
|
+
"CO2": (Code._1FC9, 00, Code._1298, 0x000000, 00, Code._2E10, 0x000000, 01, Code._10E0, 0x000000, 00, Code._31E0, 0x000000, 00, Code._1FC9, 0x000000") RFT-CO2 (04-00045)
|
|
1292
|
+
========== ================= ===================== ========================= ========================== ========================= ========================== ================= ==========
|
|
1293
|
+
|
|
1294
|
+
Leave commands:
|
|
1295
|
+
---------------
|
|
1296
|
+
|
|
1297
|
+
.. table:: leave per accessory type
|
|
1298
|
+
:widths: auto
|
|
1299
|
+
|
|
1300
|
+
========== ================= ====================== ========================= ==========
|
|
1301
|
+
type set 1 set 2 description art #
|
|
1302
|
+
========== ================= ====================== ========================= ==========
|
|
1303
|
+
"Others": (Code._1FC9, 00, Code._1FC9, 0x000000") standard leave command
|
|
1304
|
+
"AUTORFT": (Code._1FC9, 63, Code._1FC9, 0x000000") leave command of AUTO RFT (536-0150)
|
|
1305
|
+
========== ================= ====================== ========================= ==========
|
|
1306
|
+
|
|
1307
|
+
.. table:: verbs
|
|
1308
|
+
:widths: 2, 4
|
|
1309
|
+
|
|
1310
|
+
====== ========
|
|
1311
|
+
verb byte
|
|
1312
|
+
====== ========
|
|
1313
|
+
``RQ`` ``0x00``
|
|
1314
|
+
``I_`` ``0x01``
|
|
1315
|
+
``W_`` ``0x02``
|
|
1316
|
+
``RP`` ``0x03``
|
|
1317
|
+
====== ========
|
|
1258
1318
|
|
|
1259
1319
|
"""
|
ramses_rf/dispatcher.py
CHANGED
|
@@ -186,6 +186,12 @@ def process_msg(gwy: Gateway, msg: Message) -> None:
|
|
|
186
186
|
# which requires a valid payload only for 000C.
|
|
187
187
|
|
|
188
188
|
def logger_xxxx(msg: Message) -> None:
|
|
189
|
+
"""
|
|
190
|
+
Log msg according to src, code, log.debug setting.
|
|
191
|
+
|
|
192
|
+
:param msg: the Message being processed
|
|
193
|
+
:return: None
|
|
194
|
+
"""
|
|
189
195
|
if _DBG_FORCE_LOG_MESSAGES:
|
|
190
196
|
_LOGGER.warning(msg)
|
|
191
197
|
elif msg.src != gwy.hgi or (msg.code != Code._PUZZ and msg.verb != RQ):
|
|
@@ -268,10 +274,9 @@ def process_msg(gwy: Gateway, msg: Message) -> None:
|
|
|
268
274
|
|
|
269
275
|
else:
|
|
270
276
|
logger_xxxx(msg)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
# ) # why add it? passes all tests without
|
|
277
|
+
if gwy.msg_db:
|
|
278
|
+
gwy.msg_db.add(msg)
|
|
279
|
+
# why add it? enable for evohome
|
|
275
280
|
|
|
276
281
|
|
|
277
282
|
# TODO: this needs cleaning up (e.g. handle intervening packet)
|
ramses_rf/entity_base.py
CHANGED
|
@@ -67,7 +67,8 @@ if TYPE_CHECKING:
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
_QOS_TX_LIMIT = 12 # TODO: needs work
|
|
70
|
-
_ID_SLICE =
|
|
70
|
+
_ID_SLICE = 9 # base address only, legacy _msgs 9
|
|
71
|
+
_SQL_SLICE = 12 # msg_db dst field query 12
|
|
71
72
|
_SZ_LAST_PKT: Final = "last_msg"
|
|
72
73
|
_SZ_NEXT_DUE: Final = "next_due"
|
|
73
74
|
_SZ_TIMEOUT: Final = "timeout"
|
|
@@ -171,7 +172,31 @@ class _Entity:
|
|
|
171
172
|
|
|
172
173
|
|
|
173
174
|
class _MessageDB(_Entity):
|
|
174
|
-
"""Maintain/utilize an entity's state database.
|
|
175
|
+
"""Maintain/utilize an entity's state database.
|
|
176
|
+
|
|
177
|
+
EntityBase msg_db query methods
|
|
178
|
+
|
|
179
|
+
(ix = database.py.MessageIndex method)
|
|
180
|
+
|
|
181
|
+
.. table:: Database Query Methods
|
|
182
|
+
:widths: auto
|
|
183
|
+
|
|
184
|
+
==== ====================== ==================== ============ ========== ==========
|
|
185
|
+
e. method name args returns uses used by
|
|
186
|
+
==== ====================== ==================== ============ ========== ==========
|
|
187
|
+
e1 _get_msg_by_hdr hdr Message i3 discover
|
|
188
|
+
e2 _msg_value code(s), Msg, args dict[k,v] e3,e4
|
|
189
|
+
e3 _msg_value_code code, verb, key dict[k,v] e4,e5,e6 e6
|
|
190
|
+
e4 _msg_value_msg Msg, (code) dict[k,v] e2,e3
|
|
191
|
+
e5 _msg_qry_by_code_key code, key, (verb=) e6,
|
|
192
|
+
e6 _msg_value_qry_by_code key code, key str/float e3,e5
|
|
193
|
+
e7 _msg_qry sql e8
|
|
194
|
+
e8 _msg_count sql e7
|
|
195
|
+
e9 supported_cmds list(Codes) i7
|
|
196
|
+
e10 _msgs() i5
|
|
197
|
+
==== ====================== ==================== ============ ========== ==========
|
|
198
|
+
|
|
199
|
+
"""
|
|
175
200
|
|
|
176
201
|
_gwy: Gateway
|
|
177
202
|
ctl: Controller
|
|
@@ -193,18 +218,18 @@ class _MessageDB(_Entity):
|
|
|
193
218
|
self._msgs_: dict[
|
|
194
219
|
Code, Message
|
|
195
220
|
] = {} # TODO(eb): deprecated, used in test, remove Q1 2026
|
|
196
|
-
if not self._gwy.msg_db: # TODO(eb): deprecated since 0.52.
|
|
221
|
+
if not self._gwy.msg_db: # TODO(eb): deprecated since 0.52.1, remove Q1 2026
|
|
197
222
|
self._msgz_: dict[
|
|
198
223
|
Code, dict[VerbT, dict[bool | str | None, Message]]
|
|
199
224
|
] = {} # code/verb/ctx,
|
|
200
225
|
|
|
201
|
-
# As of 0.52.
|
|
226
|
+
# As of 0.52.1 we use SQLite MessageIndex, see ramses_rf/database.py
|
|
202
227
|
# _msgz_ (nested) was only used in this module. Note:
|
|
203
228
|
# _msgz (now rebuilt from _msgs) also used in: client, base, device.heat
|
|
204
229
|
|
|
205
230
|
def _handle_msg(self, msg: Message) -> None:
|
|
206
231
|
"""Store a msg in the DBs.
|
|
207
|
-
Uses SQLite MessageIndex since 0.52.
|
|
232
|
+
Uses SQLite MessageIndex since 0.52.1
|
|
208
233
|
"""
|
|
209
234
|
|
|
210
235
|
if not (
|
|
@@ -219,7 +244,13 @@ class _MessageDB(_Entity):
|
|
|
219
244
|
return # don't store the rest
|
|
220
245
|
|
|
221
246
|
if self._gwy.msg_db: # central SQLite MessageIndex
|
|
222
|
-
|
|
247
|
+
_LOGGER.debug(
|
|
248
|
+
"For %s (z_id %s) add msg %s, src %s, dst %s to msg_db.",
|
|
249
|
+
self.id,
|
|
250
|
+
self._z_id,
|
|
251
|
+
msg.src,
|
|
252
|
+
msg.dst,
|
|
253
|
+
)
|
|
223
254
|
debug_code: Code = Code._3150
|
|
224
255
|
if msg.code == debug_code and msg.src.id.startswith("01:"):
|
|
225
256
|
_LOGGER.debug(
|
|
@@ -232,11 +263,12 @@ class _MessageDB(_Entity):
|
|
|
232
263
|
# Result in test log: lookup fails
|
|
233
264
|
# msg.src = 01:073976 (CTL)
|
|
234
265
|
# Added msg from 01:073976 (CTL) with code 0005 to _gwy.msg_db
|
|
235
|
-
# query is for: 01:073976 < no suffix
|
|
266
|
+
# query is for: 01:073976 < no suffix, extended lookup to [:12] chars
|
|
267
|
+
self._gwy.msg_db.add(msg)
|
|
236
268
|
|
|
237
269
|
# ignore any replaced message that might be returned
|
|
238
270
|
else: # TODO(eb): remove Q1 2026
|
|
239
|
-
if msg.code not in self._msgz_: # deprecated since 0.52.
|
|
271
|
+
if msg.code not in self._msgz_: # deprecated since 0.52.1
|
|
240
272
|
# Store msg verb + ctx by code in nested self._msgz_ Dict
|
|
241
273
|
self._msgz_[msg.code] = {msg.verb: {msg._pkt._ctx: msg}}
|
|
242
274
|
elif msg.verb not in self._msgz_[msg.code]:
|
|
@@ -247,7 +279,7 @@ class _MessageDB(_Entity):
|
|
|
247
279
|
self._msgz_[msg.code][msg.verb][msg._pkt._ctx] = msg
|
|
248
280
|
|
|
249
281
|
# Also store msg by code in flat self._msgs_ dict (stores the latest I/RP msgs by code)
|
|
250
|
-
# TODO(eb): deprecated since 0.52.
|
|
282
|
+
# TODO(eb): deprecated since 0.52.1, remove next block _msgs_ Q1 2026
|
|
251
283
|
if msg.verb in (I_, RP): # drop RQ's
|
|
252
284
|
# if msg.code == Code._3150 and msg.src.id.startswith(
|
|
253
285
|
# "02:"
|
|
@@ -270,7 +302,12 @@ class _MessageDB(_Entity):
|
|
|
270
302
|
# safeguard against lookup failures ("sim" packets?)
|
|
271
303
|
msg_list_qry.append(self._msgs[c])
|
|
272
304
|
else:
|
|
273
|
-
_LOGGER.debug(
|
|
305
|
+
_LOGGER.debug(
|
|
306
|
+
"_msg_list could not fetch self._msgs[%s] for %s (z_id %s)",
|
|
307
|
+
c,
|
|
308
|
+
self.id,
|
|
309
|
+
self._z_id,
|
|
310
|
+
)
|
|
274
311
|
return msg_list_qry
|
|
275
312
|
# else create from legacy nested dict
|
|
276
313
|
return [m for c in self._msgz.values() for v in c.values() for m in v.values()]
|
|
@@ -315,32 +352,7 @@ class _MessageDB(_Entity):
|
|
|
315
352
|
with contextlib.suppress(KeyError):
|
|
316
353
|
del obj._msgz_[msg.code][msg.verb][msg._pkt._ctx]
|
|
317
354
|
|
|
318
|
-
|
|
319
|
-
# (ix = database.py.MessageIndex method)
|
|
320
|
-
#
|
|
321
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
322
|
-
# | e. |method name | args | returns | uses | used by |
|
|
323
|
-
# +====+======================+====================+============+==========+==========+
|
|
324
|
-
# | e1 | _get_msg_by_hdr | hdr | Message | i3 | discover |
|
|
325
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
326
|
-
# | e2 | _msg_value | code(s), Msg, args | dict[k,v] | e3,e4 | |
|
|
327
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
328
|
-
# | e3 | _msg_value_code | code, verb, key | dict[k,v] | e4,e5,e6 | e6 |
|
|
329
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
330
|
-
# | e4 | _msg_value_msg | Msg, (code) | dict[k,v] | | e2,e3 |
|
|
331
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
332
|
-
# | e5 | _msg_qry_by_code_key | code, key, (verb=) | | | e6, |
|
|
333
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
334
|
-
# | e6 | _msg_value_qry_by_code_key | code, key | str/float | e3,e5 | |
|
|
335
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
336
|
-
# | e7 | _msg_qry | sql | | | e8 |
|
|
337
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
338
|
-
# | e8 | _msg_count | sql | | e7 | |
|
|
339
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
340
|
-
# | e9 | supported_cmds | | list(Codes)| i7 | |
|
|
341
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
342
|
-
# | e10| _msgs() | | | i5 | |
|
|
343
|
-
# +----+----------------------+--------------------+------------+----------+----------+
|
|
355
|
+
### entity_base query methods
|
|
344
356
|
|
|
345
357
|
def _get_msg_by_hdr(self, hdr: HeaderT) -> Message | None:
|
|
346
358
|
"""Return a msg, if any, that matches a given header."""
|
|
@@ -399,7 +411,7 @@ class _MessageDB(_Entity):
|
|
|
399
411
|
|
|
400
412
|
assert isinstance(code, Message), (
|
|
401
413
|
f"Invalid format: _msg_value({code})"
|
|
402
|
-
) # catch invalidly formatted code, only handle Message
|
|
414
|
+
) # catch invalidly formatted code, only handle Message from here
|
|
403
415
|
return self._msg_value_msg(code, *args, **kwargs)
|
|
404
416
|
|
|
405
417
|
def _msg_value_code(
|
|
@@ -410,7 +422,7 @@ class _MessageDB(_Entity):
|
|
|
410
422
|
**kwargs: Any,
|
|
411
423
|
) -> dict | list | None:
|
|
412
424
|
"""
|
|
413
|
-
Query the message
|
|
425
|
+
Query the message dict or the SQLite index for the most recent
|
|
414
426
|
key: value pairs(s) for a given code.
|
|
415
427
|
|
|
416
428
|
:param code: filter messages by Code or a tuple of Codes, optional
|
|
@@ -442,9 +454,8 @@ class _MessageDB(_Entity):
|
|
|
442
454
|
|
|
443
455
|
elif isinstance(code, tuple):
|
|
444
456
|
msgs = [m for m in self._msgs.values() if m.code in code]
|
|
445
|
-
msg = (
|
|
446
|
-
|
|
447
|
-
) # return highest = latest? value found in code:value pairs
|
|
457
|
+
msg = max(msgs) if msgs else None
|
|
458
|
+
# return highest = latest? value found in code:value pairs
|
|
448
459
|
else:
|
|
449
460
|
msg = self._msgs.get(code)
|
|
450
461
|
|
|
@@ -458,7 +469,7 @@ class _MessageDB(_Entity):
|
|
|
458
469
|
domain_id: str | None = None,
|
|
459
470
|
) -> dict | list | None:
|
|
460
471
|
"""
|
|
461
|
-
Get from a Message all or a specific key with its value,
|
|
472
|
+
Get from a Message all or a specific key with its value(s),
|
|
462
473
|
optionally filtering for a zone or a domain
|
|
463
474
|
|
|
464
475
|
:param msg: a Message to inspect
|
|
@@ -500,17 +511,17 @@ class _MessageDB(_Entity):
|
|
|
500
511
|
or (idx == SZ_DOMAIN_ID)
|
|
501
512
|
), (
|
|
502
513
|
f"full dict:{msg_dict}, payload:{msg.payload} < Coding error: key='{idx}', val='{val}'"
|
|
503
|
-
) # should not be there (
|
|
514
|
+
) # should not be there (TODO(eb): BUG but occurs when using SQLite MessageIndex)
|
|
504
515
|
|
|
505
|
-
if
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
return
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
516
|
+
if (
|
|
517
|
+
key == "*" or not key
|
|
518
|
+
): # from a SQLite wildcard query, return first=only? k,v
|
|
519
|
+
return {
|
|
520
|
+
k: v
|
|
521
|
+
for k, v in msg_dict.items()
|
|
522
|
+
if k not in ("dhw_idx", SZ_DOMAIN_ID, SZ_ZONE_IDX) and k[:1] != "_"
|
|
523
|
+
}
|
|
524
|
+
return msg_dict.get(key)
|
|
514
525
|
|
|
515
526
|
# SQLite methods, since 0.52.0
|
|
516
527
|
|
|
@@ -518,7 +529,6 @@ class _MessageDB(_Entity):
|
|
|
518
529
|
"""
|
|
519
530
|
Retrieve from the MessageIndex a list of Code keys involving this device.
|
|
520
531
|
|
|
521
|
-
:param kwargs: not used as of 0.52.0
|
|
522
532
|
:return: list of Codes or empty list when query returned empty
|
|
523
533
|
"""
|
|
524
534
|
if self._gwy.msg_db:
|
|
@@ -530,7 +540,7 @@ class _MessageDB(_Entity):
|
|
|
530
540
|
res: list[Code] = []
|
|
531
541
|
|
|
532
542
|
for rec in self._gwy.msg_db.qry_field(
|
|
533
|
-
sql, (self.id[:
|
|
543
|
+
sql, (self.id[:_SQL_SLICE], self.id[:_SQL_SLICE])
|
|
534
544
|
):
|
|
535
545
|
_LOGGER.debug("Fetched from index: %s", rec[0])
|
|
536
546
|
# Example: "Fetched from index: code 1FD4"
|
|
@@ -550,7 +560,7 @@ class _MessageDB(_Entity):
|
|
|
550
560
|
Retrieve from the MessageIndex the most current Code for a code(s) &
|
|
551
561
|
keyword combination involving this device.
|
|
552
562
|
|
|
553
|
-
:param code: (optional) a message Code to use, e.g.
|
|
563
|
+
:param code: (optional) a message Code to use, e.g. Code._31DA or a tuple of Codes
|
|
554
564
|
:param key: (optional) message keyword to fetch, e.g. SZ_HUMIDITY
|
|
555
565
|
:param kwargs: optional verb='vb' single verb
|
|
556
566
|
:return: Code of most recent query result message or None when query returned empty
|
|
@@ -581,9 +591,11 @@ class _MessageDB(_Entity):
|
|
|
581
591
|
res = None
|
|
582
592
|
|
|
583
593
|
for rec in self._gwy.msg_db.qry_field(
|
|
584
|
-
sql, (vb, self.id[:
|
|
594
|
+
sql, (vb, self.id[:_SQL_SLICE], self.id[:_SQL_SLICE], code_qry, key)
|
|
585
595
|
):
|
|
586
|
-
_LOGGER.debug(
|
|
596
|
+
_LOGGER.debug(
|
|
597
|
+
"_msg_qry_by_code_key fetched rec: %s, code: %s", rec, code_qry
|
|
598
|
+
)
|
|
587
599
|
assert isinstance(rec[0], dt) # mypy hint
|
|
588
600
|
if rec[0] > latest: # dtm, only use most recent
|
|
589
601
|
res = Code(rec[1])
|
|
@@ -605,7 +617,7 @@ class _MessageDB(_Entity):
|
|
|
605
617
|
|
|
606
618
|
:param code: (optional) a single message Code to use, e.g. 31DA
|
|
607
619
|
:param key: (optional) message keyword to fetch the value for, e.g. SZ_HUMIDITY or * (wildcard)
|
|
608
|
-
:param kwargs: not used as of 0.52.
|
|
620
|
+
:param kwargs: not used as of 0.52.1
|
|
609
621
|
:return: a single string or float value or None when qry returned empty
|
|
610
622
|
"""
|
|
611
623
|
val_msg: dict | list | None = None
|
|
@@ -642,7 +654,7 @@ class _MessageDB(_Entity):
|
|
|
642
654
|
# """SELECT code from messages WHERE verb in (' I', 'RP') AND (src = ? OR dst = ?)
|
|
643
655
|
# AND (code = '31DA' OR ...) AND (plk LIKE '%{SZ_FAN_INFO}%' OR ...)""" = 2 params
|
|
644
656
|
for rec in self._gwy.msg_db.qry_field(
|
|
645
|
-
sql, (self.id[:
|
|
657
|
+
sql, (self.id[:_SQL_SLICE], self.id[:_SQL_SLICE])
|
|
646
658
|
):
|
|
647
659
|
_pl = self._msgs[Code(rec[0])].payload
|
|
648
660
|
# add payload dict to res(ults)
|
|
@@ -698,7 +710,7 @@ class _MessageDB(_Entity):
|
|
|
698
710
|
_msg_dict = { # ? use ctx (context) instead of just the address?
|
|
699
711
|
m.code: m
|
|
700
712
|
for m in self._gwy.msg_db.qry(
|
|
701
|
-
sql, (self.id[:
|
|
713
|
+
sql, (self.id[:_SQL_SLICE], self.id[:_SQL_SLICE])
|
|
702
714
|
) # e.g. 01:123456_HW
|
|
703
715
|
}
|
|
704
716
|
# if CTL, remove 3150, 3220 heat_demand, both are only stored on children
|
|
@@ -774,12 +786,12 @@ class _Discovery(_MessageDB):
|
|
|
774
786
|
code: CODES_SCHEMA[code][SZ_NAME]
|
|
775
787
|
for code in sorted(
|
|
776
788
|
self._gwy.msg_db.get_rp_codes(
|
|
777
|
-
(self.id[:
|
|
789
|
+
(self.id[:_SQL_SLICE], self.id[:_SQL_SLICE])
|
|
778
790
|
)
|
|
779
791
|
)
|
|
780
792
|
if self._is_not_deprecated_cmd(code)
|
|
781
793
|
}
|
|
782
|
-
return { # TODO(eb): deprecated since 0.52.
|
|
794
|
+
return { # TODO(eb): deprecated since 0.52.1, remove Q1 2026
|
|
783
795
|
code: (CODES_SCHEMA[code][SZ_NAME] if code in CODES_SCHEMA else None)
|
|
784
796
|
for code in sorted(self._msgz)
|
|
785
797
|
if self._msgz[code].get(RP) and self._is_not_deprecated_cmd(code)
|
|
@@ -806,7 +818,7 @@ class _Discovery(_MessageDB):
|
|
|
806
818
|
AND (src = ? OR dst = ?)
|
|
807
819
|
"""
|
|
808
820
|
for rec in self._gwy.msg_db.qry_field(
|
|
809
|
-
sql, (self.id[:
|
|
821
|
+
sql, (self.id[:_SQL_SLICE], self.id[:_SQL_SLICE])
|
|
810
822
|
):
|
|
811
823
|
_LOGGER.debug("Fetched OT ctx from index: %s", rec[0])
|
|
812
824
|
res.append(rec[0])
|
|
@@ -937,8 +949,8 @@ class _Discovery(_MessageDB):
|
|
|
937
949
|
sql,
|
|
938
950
|
(
|
|
939
951
|
task[_SZ_COMMAND].code,
|
|
940
|
-
self.tcs.id[:_ID_SLICE],
|
|
941
|
-
self.tcs.id[:_ID_SLICE],
|
|
952
|
+
self.tcs.id[:_ID_SLICE], # OK? not _SQL_SLICE?
|
|
953
|
+
self.tcs.id[:_ID_SLICE], # OK? not _SQL_SLICE?
|
|
942
954
|
),
|
|
943
955
|
)[0] # expect 1 Message in returned tuple
|
|
944
956
|
else: # TODO(eb) remove next Q1 2026
|
ramses_rf/gateway.py
CHANGED
|
@@ -175,6 +175,7 @@ class Gateway(Engine):
|
|
|
175
175
|
|
|
176
176
|
# initialize SQLite index, set in _tx/Engine
|
|
177
177
|
if self._sqlite_index: # TODO(eb): default to ON in Q4 2025
|
|
178
|
+
_LOGGER.info("Ramses RF starts SQLite MessageIndex")
|
|
178
179
|
self.create_sqlite_message_index() # if activated in ramses_cc > Engine
|
|
179
180
|
|
|
180
181
|
# temporarily turn on discovery, remember original state
|
|
@@ -212,7 +213,7 @@ class Gateway(Engine):
|
|
|
212
213
|
def _pause(self, *args: Any) -> None:
|
|
213
214
|
"""Pause the (unpaused) gateway (disables sending/discovery).
|
|
214
215
|
|
|
215
|
-
There is the option to save other objects, as
|
|
216
|
+
There is the option to save other objects, as `args`.
|
|
216
217
|
"""
|
|
217
218
|
_LOGGER.debug("Gateway: Pausing engine...")
|
|
218
219
|
|
|
@@ -227,7 +228,7 @@ class Gateway(Engine):
|
|
|
227
228
|
def _resume(self) -> tuple[Any]:
|
|
228
229
|
"""Resume the (paused) gateway (enables sending/discovery, if applicable).
|
|
229
230
|
|
|
230
|
-
Will restore other objects, as
|
|
231
|
+
Will restore other objects, as `args`.
|
|
231
232
|
"""
|
|
232
233
|
args: tuple[Any]
|
|
233
234
|
|
ramses_rf/schemas.py
CHANGED
ramses_rf/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ramses_rf
|
|
3
|
-
Version: 0.52.
|
|
3
|
+
Version: 0.52.2
|
|
4
4
|
Summary: A stateful RAMSES-II protocol decoder & analyser.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ramses-rf/ramses_rf
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/ramses-rf/ramses_rf/issues
|
|
@@ -4,52 +4,52 @@ ramses_cli/debug.py,sha256=vgR0lOHoYjWarN948dI617WZZGNuqHbeq6Tc16Da7b4,608
|
|
|
4
4
|
ramses_cli/discovery.py,sha256=MWoahBnAAVzfK2S7EDLsY2WYqN_ZK9L-lktrj8_4cb0,12978
|
|
5
5
|
ramses_cli/utils/cat_slow.py,sha256=AhUpM5gnegCitNKU-JGHn-DrRzSi-49ZR1Qw6lxe_t8,607
|
|
6
6
|
ramses_cli/utils/convert.py,sha256=D_YiCyX5na9pgC-_NhBlW9N1dgRKUK-uLtLBfofjzZM,1804
|
|
7
|
-
ramses_rf/__init__.py,sha256=
|
|
7
|
+
ramses_rf/__init__.py,sha256=vp2TyFGqc1fGQHsevhmaw0QEmSSCnZx7fqizKiEwHtw,1245
|
|
8
8
|
ramses_rf/binding_fsm.py,sha256=uZAOl3i19KCXqqlaLJWkEqMMP7NJBhVPW3xTikQD1fY,25996
|
|
9
9
|
ramses_rf/const.py,sha256=L3z31CZ-xqno6oZp_h-67CB_5tDDqTwSWXsqRtsjMcs,5460
|
|
10
|
-
ramses_rf/database.py,sha256=
|
|
11
|
-
ramses_rf/dispatcher.py,sha256=
|
|
12
|
-
ramses_rf/entity_base.py,sha256=
|
|
10
|
+
ramses_rf/database.py,sha256=pfCOt6wvABAaW275mNLoRnObFSQ9nT5d7PtDLMTvgr4,20340
|
|
11
|
+
ramses_rf/dispatcher.py,sha256=YjEU-QrBLo9IfoEhJo2ikg_FxOaMYoWvzelr9Vi-JZ8,11398
|
|
12
|
+
ramses_rf/entity_base.py,sha256=OPdQEeJ5zk49FKCFmRui4urFQVR2tLBS1iWCTWeZkNo,55712
|
|
13
13
|
ramses_rf/exceptions.py,sha256=mt_T7irqHSDKir6KLaf6oDglUIdrw0S40JbOrWJk5jc,3657
|
|
14
|
-
ramses_rf/gateway.py,sha256=
|
|
14
|
+
ramses_rf/gateway.py,sha256=VYZqMppU_kDYhT3EUmqpHf0LuLXPMH7ASx9jylAopWE,21218
|
|
15
15
|
ramses_rf/helpers.py,sha256=TNk_QkpIOB3alOp1sqnA9LOzi4fuDCeapNlW3zTzNas,4250
|
|
16
16
|
ramses_rf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
ramses_rf/schemas.py,sha256=
|
|
18
|
-
ramses_rf/version.py,sha256=
|
|
17
|
+
ramses_rf/schemas.py,sha256=jvpx0hZiMhaogyxbxnrbxS7m2GekKK4DFfVTNs7h-WQ,13476
|
|
18
|
+
ramses_rf/version.py,sha256=3xbuWkwVUOezgu-nlN98fGrnAgqtfivOcMVKjCTSBds,125
|
|
19
19
|
ramses_rf/device/__init__.py,sha256=sUbH5dhbYFXSoM_TPFRutpRutBRpup7_cQ9smPtDTy8,4858
|
|
20
20
|
ramses_rf/device/base.py,sha256=Yx0LZwMEb49naY8FolZ8HEBFb6XCPQBTVN_TWyO2nKg,17777
|
|
21
|
-
ramses_rf/device/heat.py,sha256=
|
|
22
|
-
ramses_rf/device/hvac.py,sha256=
|
|
21
|
+
ramses_rf/device/heat.py,sha256=hvTX32Evp23vqjXu-YHqTuneem4_QBRSxCkYR7dg4VI,54705
|
|
22
|
+
ramses_rf/device/hvac.py,sha256=gdpVACUvtq6ERMI0mwRhqIJtKYEmybzJA2NeyN1ELrs,48431
|
|
23
23
|
ramses_rf/system/__init__.py,sha256=uZLKio3gLlBzePa2aDQ1nxkcp1YXOGrn6iHTG8LiNIw,711
|
|
24
24
|
ramses_rf/system/faultlog.py,sha256=GdGmVGT3137KsTlV_nhccgIFEmYu6DFsLTn4S-8JSok,12799
|
|
25
25
|
ramses_rf/system/heat.py,sha256=3jaFEChU-HlWCRMY1y7u09s7AH4hT0pC63hnqwdmZOc,39223
|
|
26
26
|
ramses_rf/system/schedule.py,sha256=Ts6tdZPTQLV5NkgwA73tPa5QUsnZNIIuYoKC-8VsXDk,18808
|
|
27
27
|
ramses_rf/system/zones.py,sha256=9AH7ooN5QfiqvWuor2P1Dn8aILjQb2RWL9rWqDH1IjA,36075
|
|
28
|
-
ramses_tx/__init__.py,sha256=
|
|
28
|
+
ramses_tx/__init__.py,sha256=sqnjM7pUGJDmec6igTtKViSB8FLX49B5gwhAmcY9ERY,3596
|
|
29
29
|
ramses_tx/address.py,sha256=F5ZE-EbPNNom1fW9XXUILvD7DYSMBxNJvsHVliT5gjw,8452
|
|
30
|
-
ramses_tx/command.py,sha256=
|
|
30
|
+
ramses_tx/command.py,sha256=tqVECwd_QokEcRv2MSk7TUU4JSBzCZcJh1eQ0jIGgoY,125122
|
|
31
31
|
ramses_tx/const.py,sha256=pnxq5upXvLUizv9Ye_I1llD9rAa3wddHgsSkc91AIUc,30300
|
|
32
32
|
ramses_tx/exceptions.py,sha256=FJSU9YkvpKjs3yeTqUJX1o3TPFSe_B01gRGIh9b3PNc,2632
|
|
33
33
|
ramses_tx/fingerprints.py,sha256=nfftA1E62HQnb-eLt2EqjEi_la0DAoT0wt-PtTMie0s,11974
|
|
34
34
|
ramses_tx/frame.py,sha256=GzNsXr15YLeidJYGtk_xPqsZQh4ehDDlUCtT6rTDhT8,22046
|
|
35
|
-
ramses_tx/gateway.py,sha256=
|
|
35
|
+
ramses_tx/gateway.py,sha256=8o9NyEAyIui3B6HA0R8o64u4U8NZFlNl0TQ6rhwMoCk,11369
|
|
36
36
|
ramses_tx/helpers.py,sha256=J4OCRckp3JshGQTvvqEskFjB1hPS7uA_opVsuIqmZds,32915
|
|
37
37
|
ramses_tx/logger.py,sha256=qYbUoNPnPaFWKVsYvLG6uTVuPTdZ8HsMzBbGx0DpBqc,10177
|
|
38
38
|
ramses_tx/message.py,sha256=-moQ8v3HVlNSl-x3U0DDfDcj8WQ7vLqclMNxsohbmnw,13449
|
|
39
39
|
ramses_tx/opentherm.py,sha256=58PXz9l5x8Ou6Fm3y-R_UnGHCYahoi2RKIDdYStUMzk,42378
|
|
40
40
|
ramses_tx/packet.py,sha256=_qHiPFWpQpKueZOgf1jJ93Y09iZjo3LZWStLglVkXg4,7370
|
|
41
|
-
ramses_tx/parsers.py,sha256=
|
|
41
|
+
ramses_tx/parsers.py,sha256=Z0ochrNyO2l8SAP0oJxN-tx2nJluEaNP_uJCIm_lsA8,111279
|
|
42
42
|
ramses_tx/protocol.py,sha256=9R3aCzuiWEyXmugmB5tyR_RhBlgfnpUXj7AsMP9BzzU,28867
|
|
43
43
|
ramses_tx/protocol_fsm.py,sha256=ZKtehCr_4TaDdfdlfidFLJaOVTYtaEq5h4tLqNIhb9s,26827
|
|
44
44
|
ramses_tx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
45
|
ramses_tx/ramses.py,sha256=NG81GBNZlap-Gi9ac-r6OFE-KaHvXsgPDWy-I2Irr-4,53698
|
|
46
|
-
ramses_tx/schemas.py,sha256=
|
|
47
|
-
ramses_tx/transport.py,sha256=
|
|
46
|
+
ramses_tx/schemas.py,sha256=U8SkwbW41zac0MQe0NgP6qTUqymmbeCG-UHPEw_GAv0,13267
|
|
47
|
+
ramses_tx/transport.py,sha256=kyvnVf2RpuA_8YRO6C4S60YHctDag0ywfoMzb0wWDGY,58552
|
|
48
48
|
ramses_tx/typed_dicts.py,sha256=w-0V5t2Q3GiNUOrRAWiW9GtSwbta_7luME6GfIb1zhI,10869
|
|
49
49
|
ramses_tx/typing.py,sha256=eF2SlPWhNhEFQj6WX2AhTXiyRQVXYnFutiepllYl2rI,5042
|
|
50
|
-
ramses_tx/version.py,sha256=
|
|
51
|
-
ramses_rf-0.52.
|
|
52
|
-
ramses_rf-0.52.
|
|
53
|
-
ramses_rf-0.52.
|
|
54
|
-
ramses_rf-0.52.
|
|
55
|
-
ramses_rf-0.52.
|
|
50
|
+
ramses_tx/version.py,sha256=NEkkAJQW7Hx8GmvkR9BZpLSOi30OMi9Awth13sWXuTQ,123
|
|
51
|
+
ramses_rf-0.52.2.dist-info/METADATA,sha256=Fm3vwa0FvFkMEHz44B8FDq0PjboTn_g9sGHyK7HiydA,4000
|
|
52
|
+
ramses_rf-0.52.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
53
|
+
ramses_rf-0.52.2.dist-info/entry_points.txt,sha256=NnyK29baOCNg8DinPYiZ368h7MTH7bgTW26z2A1NeIE,50
|
|
54
|
+
ramses_rf-0.52.2.dist-info/licenses/LICENSE,sha256=-Kc35W7l1UkdiQ4314_yVWv7vDDrg7IrJfMLUiq6Nfs,1074
|
|
55
|
+
ramses_rf-0.52.2.dist-info/RECORD,,
|
ramses_tx/__init__.py
CHANGED
ramses_tx/command.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
2
|
+
"""
|
|
3
|
+
RAMSES RF - a RAMSES-II protocol decoder & analyser.
|
|
3
4
|
|
|
4
5
|
This module provides the `Command` class for constructing and managing RAMSES-II protocol
|
|
5
6
|
commands (packets) that are to be sent to HVAC devices. It includes methods for creating
|
|
@@ -1394,7 +1395,7 @@ class Command(Frame):
|
|
|
1394
1395
|
|
|
1395
1396
|
:param ctl_id: The device ID of the DHW controller
|
|
1396
1397
|
:type ctl_id: DeviceIdT | str
|
|
1397
|
-
:param
|
|
1398
|
+
:param kwargs: Additional parameters (currently only 'dhw_idx' is supported)
|
|
1398
1399
|
:key dhw_idx: The DHW circuit index (0 or 1, defaults to 0 for single-DHW systems)
|
|
1399
1400
|
:type dhw_idx: int, optional
|
|
1400
1401
|
:return: A Command object for the RQ|1F41 message
|
|
@@ -1438,7 +1439,7 @@ class Command(Frame):
|
|
|
1438
1439
|
:type until: datetime | str | None
|
|
1439
1440
|
:param duration: Duration in seconds for temporary mode (alternative to 'until')
|
|
1440
1441
|
:type duration: int | None
|
|
1441
|
-
:param
|
|
1442
|
+
:param kwargs: Additional parameters (currently only 'dhw_idx' is supported)
|
|
1442
1443
|
:key dhw_idx: The DHW circuit index (0 or 1, defaults to 0 for single-DHW systems)
|
|
1443
1444
|
:type dhw_idx: int, optional
|
|
1444
1445
|
:return: A Command object for the W|1F41 message
|
|
@@ -1501,7 +1502,7 @@ class Command(Frame):
|
|
|
1501
1502
|
:type codes: Code | Iterable[Code] | None
|
|
1502
1503
|
:param dst_id: Optional destination device ID (defaults to broadcast)
|
|
1503
1504
|
:type dst_id: DeviceIdT | str | None
|
|
1504
|
-
:param
|
|
1505
|
+
:param kwargs: Additional parameters
|
|
1505
1506
|
:key oem_code: OEM code for bind offers (only used with I-type messages)
|
|
1506
1507
|
:type oem_code: str, optional
|
|
1507
1508
|
:return: A Command object for the bind operation
|
|
@@ -2531,7 +2532,7 @@ class Command(Frame):
|
|
|
2531
2532
|
:type supply_flow: float | None
|
|
2532
2533
|
:param exhaust_flow: Current exhaust air flow rate (if available)
|
|
2533
2534
|
:type exhaust_flow: float | None
|
|
2534
|
-
:param
|
|
2535
|
+
:param kwargs: Additional parameters (reserved for future use)
|
|
2535
2536
|
:return: A configured Command object for the HVAC fan status update
|
|
2536
2537
|
:rtype: Command
|
|
2537
2538
|
|
ramses_tx/gateway.py
CHANGED
|
@@ -113,7 +113,7 @@ class Engine:
|
|
|
113
113
|
self._include,
|
|
114
114
|
self._exclude,
|
|
115
115
|
)
|
|
116
|
-
self._sqlite_index = kwargs.pop(SZ_SQLITE_INDEX)
|
|
116
|
+
self._sqlite_index = kwargs.pop(SZ_SQLITE_INDEX, False) # default True?
|
|
117
117
|
self._kwargs: dict[str, Any] = kwargs # HACK
|
|
118
118
|
|
|
119
119
|
self._engine_lock = Lock() # FIXME: threading lock, or asyncio lock?
|
ramses_tx/parsers.py
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
NOTES: aspirations on a consistent Schema, going forward:
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
mode (
|
|
8
|
-
|
|
6
|
+
============== ======== =================================== ========================
|
|
7
|
+
:mode/state: :bool: :mutex (infinitive. vs -ing): :flags:
|
|
8
|
+
mode (config.) enabled disabled, heat, cool, heat_cool... ch_enabled, dhw_enabled
|
|
9
|
+
state (action) active idle, heating, cooling... is_heating, is_cooling
|
|
10
|
+
============== ======== =================================== ========================
|
|
9
11
|
|
|
10
12
|
- prefer: enabled: True over xx_enabled: True (if only ever 1 flag)
|
|
11
13
|
- prefer: active: True over is_heating: True (if only ever 1 flag)
|
|
@@ -362,8 +364,9 @@ def parser_0009(payload: str, msg: Message) -> dict | list[dict]: # TODO: only
|
|
|
362
364
|
|
|
363
365
|
The failsafe mode defines the relay behaviour if the RF communication is lost (e.g.
|
|
364
366
|
when a room thermostat stops communicating due to discharged batteries):
|
|
365
|
-
|
|
366
|
-
|
|
367
|
+
|
|
368
|
+
- False (disabled) - if RF comms are lost, relay will be held in OFF position
|
|
369
|
+
- True (enabled) - if RF comms are lost, relay will cycle at 20% ON, 80% OFF
|
|
367
370
|
|
|
368
371
|
This setting may need to be enabled to ensure frost protect mode.
|
|
369
372
|
"""
|
|
@@ -2289,45 +2292,46 @@ def parser_31da(payload: str, msg: Message) -> PayDictT._31DA:
|
|
|
2289
2292
|
def parser_31e0(payload: str, msg: Message) -> dict | list[dict]: # TODO: only dict
|
|
2290
2293
|
"""Notes are.
|
|
2291
2294
|
|
|
2292
|
-
van means
|
|
2295
|
+
"van" means "of".
|
|
2293
2296
|
- 0 = min. van min. potm would be:
|
|
2294
2297
|
- 0 = minimum of minimum potentiometer
|
|
2295
2298
|
|
|
2296
2299
|
See: https://www.industrialcontrolsonline.com/honeywell-t991a
|
|
2297
2300
|
- modulates air temperatures in ducts
|
|
2298
|
-
|
|
2299
|
-
case 0x31E0: ' 12768:
|
|
2300
|
-
{
|
|
2301
|
-
string str4;
|
|
2302
|
-
unchecked
|
|
2303
|
-
{
|
|
2304
|
-
result.Fan = Conversions.ToString((double)(int)data[checked(start + 1)] / 2.0);
|
|
2305
|
-
str4 = "";
|
|
2306
|
-
}
|
|
2307
|
-
str4 = (data[start + 2] & 0xF) switch
|
|
2308
|
-
{
|
|
2309
|
-
0 => str4 + "0 = min. potm. ",
|
|
2310
|
-
1 => str4 + "0 = min. van min. potm ",
|
|
2311
|
-
2 => str4 + "0 = min. fan ",
|
|
2312
|
-
_ => "",
|
|
2313
|
-
};
|
|
2314
|
-
switch (data[start + 2] & 0xF0)
|
|
2315
|
-
{
|
|
2316
|
-
case 16:
|
|
2317
|
-
str4 += "100 = max. potm";
|
|
2318
|
-
break;
|
|
2319
|
-
case 32:
|
|
2320
|
-
str4 += "100 = max. van max. potm ";
|
|
2321
|
-
break;
|
|
2322
|
-
case 48:
|
|
2323
|
-
str4 += "100 = max. fan ";
|
|
2324
|
-
break;
|
|
2325
|
-
}
|
|
2326
|
-
result.Data = str4;
|
|
2327
|
-
break;
|
|
2328
|
-
}
|
|
2329
2301
|
"""
|
|
2330
2302
|
|
|
2303
|
+
# coding note:
|
|
2304
|
+
# case 0x31E0: ' 12768:
|
|
2305
|
+
# {
|
|
2306
|
+
# string str4;
|
|
2307
|
+
# unchecked
|
|
2308
|
+
# {
|
|
2309
|
+
# result.Fan = Conversions.ToString((double)(int)data[checked(start + 1)] / 2.0);
|
|
2310
|
+
# str4 = "";
|
|
2311
|
+
# }
|
|
2312
|
+
# str4 = (data[start + 2] & 0xF) switch
|
|
2313
|
+
# {
|
|
2314
|
+
# 0 => str4 + "0 = min. potm. ",
|
|
2315
|
+
# 1 => str4 + "0 = min. van min. potm ",
|
|
2316
|
+
# 2 => str4 + "0 = min. fan ",
|
|
2317
|
+
# _ => "",
|
|
2318
|
+
# };
|
|
2319
|
+
# switch (data[start + 2] & 0xF0)
|
|
2320
|
+
# {
|
|
2321
|
+
# case 16:
|
|
2322
|
+
# str4 += "100 = max. potm";
|
|
2323
|
+
# break;
|
|
2324
|
+
# case 32:
|
|
2325
|
+
# str4 += "100 = max. van max. potm ";
|
|
2326
|
+
# break;
|
|
2327
|
+
# case 48:
|
|
2328
|
+
# str4 += "100 = max. fan ";
|
|
2329
|
+
# break;
|
|
2330
|
+
# }
|
|
2331
|
+
# result.Data = str4;
|
|
2332
|
+
# break;
|
|
2333
|
+
# }
|
|
2334
|
+
|
|
2331
2335
|
# .I --- 37:005302 32:132403 --:------ 31E0 008 00-0000-00 01-0064-00 # RF15 CO2 to Orcon HRC400 series SmartComfort Valve
|
|
2332
2336
|
|
|
2333
2337
|
# .I --- 29:146052 32:023459 --:------ 31E0 003 00-0000
|
ramses_tx/schemas.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""RAMSES RF - a RAMSES-II protocol decoder & analyser.
|
|
3
3
|
|
|
4
|
-
Schema processor for protocol (lower) layer.
|
|
4
|
+
:term:`Schema` processor for protocol (lower) layer.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
@@ -73,13 +73,17 @@ class PktLogConfigT(TypedDict):
|
|
|
73
73
|
def sch_packet_log_dict_factory(
|
|
74
74
|
default_backups: int = 0,
|
|
75
75
|
) -> dict[vol.Required, vol.Any]:
|
|
76
|
-
"""
|
|
76
|
+
"""
|
|
77
|
+
:return: a packet log dict with a configurable default rotation policy.
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
Usage:
|
|
80
|
+
|
|
81
|
+
.. code-block::
|
|
82
|
+
|
|
83
|
+
SCH_PACKET_LOG_7 = vol.Schema(
|
|
84
|
+
packet_log_dict_factory(default_backups=7), extra=vol.PREVENT_EXTRA
|
|
85
|
+
)
|
|
79
86
|
|
|
80
|
-
SCH_PACKET_LOG_7 = vol.Schema(
|
|
81
|
-
packet_log_dict_factory(default_backups=7), extra=vol.PREVENT_EXTRA
|
|
82
|
-
)
|
|
83
87
|
"""
|
|
84
88
|
|
|
85
89
|
SCH_PACKET_LOG_CONFIG = vol.Schema(
|
|
@@ -162,11 +166,13 @@ class PortConfigT(TypedDict):
|
|
|
162
166
|
def sch_serial_port_dict_factory() -> dict[vol.Required, vol.Any]:
|
|
163
167
|
"""Return a serial port dict.
|
|
164
168
|
|
|
165
|
-
|
|
169
|
+
Usage:
|
|
166
170
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
.. code-block::
|
|
172
|
+
|
|
173
|
+
SCH_SERIAL_PORT = vol.Schema(
|
|
174
|
+
sch_serial_port_dict_factory(), extra=vol.PREVENT_EXTRA
|
|
175
|
+
)
|
|
170
176
|
"""
|
|
171
177
|
|
|
172
178
|
SCH_SERIAL_PORT_NAME = str
|
|
@@ -252,11 +258,13 @@ def sch_global_traits_dict_factory(
|
|
|
252
258
|
) -> tuple[dict[vol.Optional, vol.Any], vol.Any]:
|
|
253
259
|
"""Return a global traits dict with a configurable extra traits.
|
|
254
260
|
|
|
255
|
-
|
|
261
|
+
Usage:
|
|
256
262
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
263
|
+
.. code-block::
|
|
264
|
+
|
|
265
|
+
SCH_GLOBAL_TRAITS = vol.Schema(
|
|
266
|
+
sch_global_traits_dict(heat=traits), extra=vol.PREVENT_EXTRA
|
|
267
|
+
)
|
|
260
268
|
"""
|
|
261
269
|
|
|
262
270
|
heat_traits = heat_traits or {}
|
|
@@ -348,7 +356,8 @@ def select_device_filter_mode(
|
|
|
348
356
|
known_list: DeviceListT,
|
|
349
357
|
block_list: DeviceListT,
|
|
350
358
|
) -> bool:
|
|
351
|
-
"""
|
|
359
|
+
"""
|
|
360
|
+
Determine which device filter to use, if any.
|
|
352
361
|
|
|
353
362
|
Either:
|
|
354
363
|
- block if device_id in block_list (could be empty), otherwise
|
ramses_tx/transport.py
CHANGED
|
@@ -3,30 +3,38 @@
|
|
|
3
3
|
|
|
4
4
|
Operates at the pkt layer of: app - msg - pkt - h/w
|
|
5
5
|
|
|
6
|
-
For ser2net, use the following YAML with: ser2net -c misc/ser2net.yaml
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
For ser2net, use the following YAML with: ``ser2net -c misc/ser2net.yaml``
|
|
7
|
+
|
|
8
|
+
.. code-block::
|
|
9
|
+
|
|
10
|
+
connection: &con00
|
|
11
|
+
accepter: telnet(rfc2217),tcp,5001
|
|
12
|
+
timeout: 0
|
|
13
|
+
connector: serialdev,/dev/ttyUSB0,115200n81,local
|
|
14
|
+
options:
|
|
15
|
+
max-connections: 3
|
|
16
|
+
|
|
17
|
+
For ``socat``, see:
|
|
18
|
+
|
|
19
|
+
.. code-block::
|
|
20
|
+
|
|
21
|
+
socat -dd pty,raw,echo=0 pty,raw,echo=0
|
|
22
|
+
python client.py monitor /dev/pts/0
|
|
23
|
+
cat packet.log | cut -d ' ' -f 2- | unix2dos > /dev/pts/1
|
|
18
24
|
|
|
19
25
|
For re-flashing evofw3 via Arduino IDE on *my* atmega328p (YMMV):
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
|
|
27
|
+
- Board: atmega328p (SW UART)
|
|
28
|
+
- Bootloader: Old Bootloader
|
|
29
|
+
- Processor: atmega328p (5V, 16 MHz)
|
|
30
|
+
- Host: 57600 (or 115200, YMMV)
|
|
31
|
+
- Pinout: Nano
|
|
25
32
|
|
|
26
33
|
For re-flashing evofw3 via Arduino IDE on *my* atmega32u4 (YMMV):
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
|
|
35
|
+
- Board: atmega32u4 (HW UART)
|
|
36
|
+
- Processor: atmega32u4 (5V, 16 MHz)
|
|
37
|
+
- Pinout: Pro Micro
|
|
30
38
|
"""
|
|
31
39
|
|
|
32
40
|
from __future__ import annotations
|
|
@@ -250,11 +258,12 @@ async def is_hgi80(serial_port: SerPortNameT) -> bool | None:
|
|
|
250
258
|
|
|
251
259
|
|
|
252
260
|
def _normalise(pkt_line: str) -> str:
|
|
253
|
-
"""
|
|
261
|
+
"""
|
|
262
|
+
Perform any (transparent) frame-level hacks, as required at (near-)RF layer.
|
|
254
263
|
|
|
255
264
|
Goals:
|
|
256
265
|
- ensure an evofw3 provides the same output as a HGI80 (none, presently)
|
|
257
|
-
- handle 'strange' packets (e.g. I|08:|0008)
|
|
266
|
+
- handle 'strange' packets (e.g. ``I|08:|0008``)
|
|
258
267
|
"""
|
|
259
268
|
|
|
260
269
|
# TODO: deprecate as only for ramses_esp <0.4.0
|
ramses_tx/version.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|