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/dispatcher.py
CHANGED
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
"""RAMSES RF - Decode/process a message (payload into JSON)."""
|
|
3
|
+
|
|
4
|
+
# TODO:
|
|
5
|
+
# - fix dispatching - what devices (some are Addr) are sent packets, esp. 1FC9s
|
|
5
6
|
|
|
6
|
-
Decode/process a message (payload into JSON).
|
|
7
|
-
"""
|
|
8
7
|
from __future__ import annotations
|
|
9
8
|
|
|
9
|
+
import contextlib
|
|
10
10
|
import logging
|
|
11
11
|
from datetime import timedelta as td
|
|
12
|
+
from typing import TYPE_CHECKING, Final
|
|
13
|
+
|
|
14
|
+
from ramses_tx import ALL_DEV_ADDR, CODES_BY_DEV_SLUG, Message
|
|
15
|
+
from ramses_tx.ramses import (
|
|
16
|
+
CODES_OF_HEAT_DOMAIN,
|
|
17
|
+
CODES_OF_HEAT_DOMAIN_ONLY,
|
|
18
|
+
CODES_OF_HVAC_DOMAIN_ONLY,
|
|
19
|
+
)
|
|
12
20
|
|
|
21
|
+
from . import exceptions as exc
|
|
13
22
|
from .const import (
|
|
14
|
-
DEV_TYPE,
|
|
15
23
|
DEV_TYPE_MAP,
|
|
16
24
|
DONT_CREATE_ENTITIES,
|
|
17
25
|
DONT_UPDATE_ENTITIES,
|
|
18
26
|
SZ_DEVICES,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
from .protocol import CODES_BY_DEV_SLUG, CODES_SCHEMA, Message
|
|
23
|
-
from .protocol.exceptions import EvohomeError, InvalidAddrSetError, InvalidPacketError
|
|
24
|
-
from .protocol.ramses import (
|
|
25
|
-
CODES_OF_HEAT_DOMAIN,
|
|
26
|
-
CODES_OF_HEAT_DOMAIN_ONLY,
|
|
27
|
-
CODES_OF_HVAC_DOMAIN_ONLY,
|
|
27
|
+
SZ_OFFER,
|
|
28
|
+
SZ_PHASE,
|
|
29
|
+
DevType,
|
|
28
30
|
)
|
|
31
|
+
from .device import Device, Fakeable
|
|
29
32
|
|
|
30
|
-
#
|
|
31
|
-
from .protocol import ( # noqa: F401, isort: skip, pylint: disable=unused-import
|
|
33
|
+
from .const import ( # noqa: F401, isort: skip, pylint: disable=unused-import
|
|
32
34
|
I_,
|
|
33
35
|
RP,
|
|
34
36
|
RQ,
|
|
@@ -36,45 +38,52 @@ from .protocol import ( # noqa: F401, isort: skip, pylint: disable=unused-impor
|
|
|
36
38
|
Code,
|
|
37
39
|
)
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from . import Gateway
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
#
|
|
45
|
+
# NOTE: All debug flags should be False for deployment to end-users
|
|
46
|
+
_DBG_FORCE_LOG_MESSAGES: Final[bool] = False # useful for dev/test
|
|
47
|
+
_DBG_INCREASE_LOG_LEVELS: Final[bool] = (
|
|
48
|
+
False # set True for developer-friendly log spam
|
|
49
|
+
)
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
MSG_FORMAT_18 = "|| {:18s} | {:18s} | {:2s} | {:16s} | {:^4s} || {}"
|
|
51
|
+
_LOGGER = logging.getLogger(__name__)
|
|
45
52
|
|
|
46
|
-
DEV_MODE = __dev_mode__ and False # or True # set True for useful Tracebacks
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
__all__ = ["detect_array_fragment", "process_msg"]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
MSG_FORMAT_18 = "|| {:18s} | {:18s} | {:2s} | {:16s} | {:^4s} || {}"
|
|
51
58
|
|
|
52
|
-
|
|
59
|
+
_TD_SECONDS_003 = td(seconds=3)
|
|
53
60
|
|
|
54
61
|
|
|
55
|
-
def _create_devices_from_addrs(gwy, this: Message) -> None:
|
|
62
|
+
def _create_devices_from_addrs(gwy: Gateway, this: Message) -> None:
|
|
56
63
|
"""Discover and create any new devices using the packet addresses (not payload)."""
|
|
57
64
|
|
|
65
|
+
# FIXME: changing Address to Devices is messy: ? Protocol for same method signatures
|
|
58
66
|
# prefer Devices but can continue with Addresses if required...
|
|
59
|
-
this.src = gwy.device_by_id.get(this.src.id, this.src)
|
|
60
|
-
this.dst = gwy.device_by_id.get(this.dst.id, this.dst)
|
|
67
|
+
this.src = gwy.device_by_id.get(this.src.id, this.src) # type: ignore[assignment]
|
|
68
|
+
this.dst = gwy.device_by_id.get(this.dst.id, this.dst) # type: ignore[assignment]
|
|
61
69
|
|
|
62
70
|
# Devices need to know their controller, ?and their location ('parent' domain)
|
|
63
71
|
# NB: only addrs processed here, packet metadata is processed elsewhere
|
|
64
72
|
|
|
65
73
|
# Determinging bindings to a controller:
|
|
66
|
-
# - configury; As per any schema
|
|
74
|
+
# - configury; As per any schema # codespell:ignore configury
|
|
67
75
|
# - discovery: If in 000C pkt, or pkt *to* device where src is a controller
|
|
68
76
|
# - eavesdrop: If pkt *from* device where dst is a controller
|
|
69
77
|
|
|
70
78
|
# Determinging location in a schema (domain/DHW/zone):
|
|
71
|
-
# - configury; As per any schema
|
|
79
|
+
# - configury; As per any schema # codespell:ignore configury
|
|
72
80
|
# - discovery: If in 000C pkt - unable for 10: & 00: (TRVs)
|
|
73
81
|
# - discovery: from packet fingerprint, excl. payloads (only for 10:)
|
|
74
82
|
# - eavesdrop: from packet fingerprint, incl. payloads
|
|
75
83
|
|
|
76
|
-
if not isinstance(this.src, Device):
|
|
77
|
-
|
|
84
|
+
if not isinstance(this.src, Device): # type: ignore[unreachable]
|
|
85
|
+
# may: LookupError, but don't suppress
|
|
86
|
+
this.src = gwy.get_device(this.src.id) # type: ignore[assignment]
|
|
78
87
|
if this.dst.id == this.src.id:
|
|
79
88
|
this.dst = this.src
|
|
80
89
|
return
|
|
@@ -82,14 +91,12 @@ def _create_devices_from_addrs(gwy, this: Message) -> None:
|
|
|
82
91
|
if not gwy.config.enable_eavesdrop:
|
|
83
92
|
return
|
|
84
93
|
|
|
85
|
-
if not isinstance(this.dst, Device) and this.src is not gwy.hgi:
|
|
86
|
-
|
|
87
|
-
this.dst = gwy.get_device(this.dst.id) #
|
|
88
|
-
except LookupError:
|
|
89
|
-
pass
|
|
94
|
+
if not isinstance(this.dst, Device) and this.src is not gwy.hgi: # type: ignore[unreachable]
|
|
95
|
+
with contextlib.suppress(LookupError):
|
|
96
|
+
this.dst = gwy.get_device(this.dst.id) # type: ignore[assignment]
|
|
90
97
|
|
|
91
98
|
|
|
92
|
-
def _check_msg_addrs(msg: Message) -> None:
|
|
99
|
+
def _check_msg_addrs(msg: Message) -> None: # TODO
|
|
93
100
|
"""Validate the packet's address set.
|
|
94
101
|
|
|
95
102
|
Raise InvalidAddrSetError if the meta data is invalid, otherwise simply return.
|
|
@@ -106,7 +113,9 @@ def _check_msg_addrs(msg: Message) -> None:
|
|
|
106
113
|
# .I --- 01:078710 --:------ 01:144246 1F09 003 FF04B5 # invalid
|
|
107
114
|
# .I --- 29:151550 29:237552 --:------ 22F3 007 00023C03040000 # valid? HVAC
|
|
108
115
|
if msg.code in CODES_OF_HEAT_DOMAIN_ONLY:
|
|
109
|
-
raise
|
|
116
|
+
raise exc.PacketAddrSetInvalid(
|
|
117
|
+
f"Invalid addr pair: {msg.src!r}/{msg.dst!r}"
|
|
118
|
+
)
|
|
110
119
|
elif msg.code in CODES_OF_HEAT_DOMAIN:
|
|
111
120
|
_LOGGER.warning(
|
|
112
121
|
f"{msg!r} < Invalid addr pair: {msg.src!r}/{msg.dst!r}, is it HVAC?"
|
|
@@ -117,197 +126,164 @@ def _check_msg_addrs(msg: Message) -> None:
|
|
|
117
126
|
)
|
|
118
127
|
|
|
119
128
|
|
|
120
|
-
def
|
|
121
|
-
"""Validate the packet's source device class
|
|
122
|
-
|
|
123
|
-
Raise InvalidPacketError if the meta data is invalid, otherwise simply return.
|
|
124
|
-
"""
|
|
129
|
+
def _check_src_slug(msg: Message, *, slug: str | None = None) -> None:
|
|
130
|
+
"""Validate the packet's source device class against its verb/code pair."""
|
|
125
131
|
|
|
126
132
|
if slug is None: # slug = best_dev_role(msg.src, msg=msg)._SLUG
|
|
127
|
-
slug = getattr(msg.src, "_SLUG",
|
|
128
|
-
if slug in (
|
|
133
|
+
slug = getattr(msg.src, "_SLUG", None)
|
|
134
|
+
if slug in (None, DevType.HGI, DevType.DEV, DevType.HEA, DevType.HVC):
|
|
129
135
|
return # TODO: use DEV_TYPE_MAP.PROMOTABLE_SLUGS
|
|
130
136
|
|
|
131
137
|
if slug not in CODES_BY_DEV_SLUG:
|
|
132
|
-
|
|
133
|
-
err_msg = f"Unknown src type: {msg.dst}"
|
|
134
|
-
if STRICT_MODE:
|
|
135
|
-
raise InvalidPacketError(err_msg)
|
|
136
|
-
(_LOGGER.warning if DEV_MODE else _LOGGER.info)(f"{msg!r} < {err_msg}")
|
|
137
|
-
return
|
|
138
|
-
_LOGGER.warning(f"{msg!r} < Unknown src type: {msg.src}, is it HVAC?")
|
|
139
|
-
return
|
|
138
|
+
raise exc.PacketInvalid(f"{msg!r} < Unknown src slug ({slug}), is it HVAC?")
|
|
140
139
|
|
|
141
140
|
#
|
|
142
|
-
#
|
|
143
141
|
|
|
144
|
-
if msg.code not in CODES_BY_DEV_SLUG[slug]:
|
|
145
|
-
|
|
146
|
-
err_msg = f"Invalid code for {msg.src} to Tx: {msg.code}"
|
|
147
|
-
if STRICT_MODE:
|
|
148
|
-
raise InvalidPacketError(err_msg)
|
|
149
|
-
(_LOGGER.warning if DEV_MODE else _LOGGER.info)(f"{msg!r} < {err_msg}")
|
|
150
|
-
return
|
|
151
|
-
if msg.verb in (RQ, W_):
|
|
152
|
-
return
|
|
153
|
-
(_LOGGER.warning if DEV_MODE else _LOGGER.info)(
|
|
154
|
-
f"{msg!r} < Invalid code for {msg.src} to Tx: {msg.code}"
|
|
155
|
-
)
|
|
156
|
-
return
|
|
142
|
+
if msg.code not in CODES_BY_DEV_SLUG[slug]:
|
|
143
|
+
raise exc.PacketInvalid(f"{msg!r} < Unexpected code for src ({slug}) to Tx")
|
|
157
144
|
|
|
158
145
|
#
|
|
159
146
|
#
|
|
160
147
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if STRICT_MODE:
|
|
166
|
-
raise InvalidPacketError(err_msg)
|
|
167
|
-
(_LOGGER.warning if DEV_MODE else _LOGGER.info)(f"{msg!r} < {err_msg}")
|
|
168
|
-
|
|
148
|
+
if msg.verb not in CODES_BY_DEV_SLUG[slug][msg.code]:
|
|
149
|
+
raise exc.PacketInvalid(
|
|
150
|
+
f"{msg!r} < Unexpected verb/code for src ({slug}) to Tx"
|
|
151
|
+
)
|
|
169
152
|
|
|
170
|
-
def _check_msg_dst(msg: Message, *, slug: str = None) -> None:
|
|
171
|
-
"""Validate the packet's destination device class (type) against its verb/code pair.
|
|
172
153
|
|
|
173
|
-
|
|
174
|
-
"""
|
|
154
|
+
def _check_dst_slug(msg: Message, *, slug: str | None = None) -> None:
|
|
155
|
+
"""Validate the packet's destination device class against its verb/code pair."""
|
|
175
156
|
|
|
176
157
|
if slug is None:
|
|
177
158
|
slug = getattr(msg.dst, "_SLUG", None)
|
|
178
|
-
if slug in (None,
|
|
159
|
+
if slug in (None, DevType.HGI, DevType.DEV, DevType.HEA, DevType.HVC):
|
|
179
160
|
return # TODO: use DEV_TYPE_MAP.PROMOTABLE_SLUGS
|
|
180
161
|
|
|
181
162
|
if slug not in CODES_BY_DEV_SLUG:
|
|
182
|
-
|
|
183
|
-
err_msg = f"Unknown dst type: {msg.dst}"
|
|
184
|
-
if STRICT_MODE:
|
|
185
|
-
raise InvalidPacketError(err_msg)
|
|
186
|
-
(_LOGGER.warning if DEV_MODE else _LOGGER.info)(f"{msg!r} < {err_msg}")
|
|
187
|
-
return
|
|
188
|
-
_LOGGER.warning(f"{msg!r} < Unknown dst type: {msg.dst}, is it HVAC?")
|
|
189
|
-
return
|
|
163
|
+
raise exc.PacketInvalid(f"{msg!r} < Unknown dst slug ({slug}), is it HVAC?")
|
|
190
164
|
|
|
191
|
-
if msg.verb == I_: # TODO: not common, unless src=dst
|
|
192
|
-
return # receiving an I isn't currently in the schema & cant yet be tested
|
|
193
165
|
if f"{slug}/{msg.verb}/{msg.code}" in (f"CTL/{RQ}/{Code._3EF1}",):
|
|
194
166
|
return # HACK: an exception-to-the-rule that need sorting
|
|
195
167
|
|
|
196
|
-
if msg.code not in CODES_BY_DEV_SLUG[slug]:
|
|
197
|
-
|
|
198
|
-
err_msg = f"Invalid code for {msg.dst} to Rx: {msg.code}"
|
|
199
|
-
if STRICT_MODE:
|
|
200
|
-
raise InvalidPacketError(err_msg)
|
|
201
|
-
(_LOGGER.warning if DEV_MODE else _LOGGER.info)(f"{msg!r} < {err_msg}")
|
|
202
|
-
return
|
|
203
|
-
if msg.verb == RP:
|
|
204
|
-
return
|
|
205
|
-
(_LOGGER.warning if DEV_MODE else _LOGGER.info)(
|
|
206
|
-
f"{msg!r} < Invalid code for {msg.dst} to Rx/Tx: {msg.code}"
|
|
207
|
-
)
|
|
208
|
-
return
|
|
168
|
+
if msg.code not in CODES_BY_DEV_SLUG[slug]:
|
|
169
|
+
raise exc.PacketInvalid(f"{msg!r} < Unexpected code for dst ({slug}) to Rx")
|
|
209
170
|
|
|
210
171
|
if f"{msg.verb}/{msg.code}" in (f"{W_}/{Code._0001}",):
|
|
211
172
|
return # HACK: an exception-to-the-rule that need sorting
|
|
212
|
-
if f"{slug}/{msg.verb}/{msg.code}" in (f"{
|
|
173
|
+
if f"{slug}/{msg.verb}/{msg.code}" in (f"{DevType.BDR}/{RQ}/{Code._3EF0}",):
|
|
213
174
|
return # HACK: an exception-to-the-rule that need sorting
|
|
214
175
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if STRICT_MODE:
|
|
220
|
-
raise InvalidPacketError(err_msg)
|
|
221
|
-
(_LOGGER.warning if DEV_MODE else _LOGGER.info)(f"{msg!r} < {err_msg}")
|
|
176
|
+
if {RQ: RP, RP: RQ, W_: I_}[msg.verb] not in CODES_BY_DEV_SLUG[slug][msg.code]:
|
|
177
|
+
raise exc.PacketInvalid(
|
|
178
|
+
f"{msg!r} < Unexpected verb/code for dst ({slug}) to Rx"
|
|
179
|
+
)
|
|
222
180
|
|
|
223
181
|
|
|
224
|
-
def process_msg(
|
|
182
|
+
def process_msg(gwy: Gateway, msg: Message) -> None:
|
|
225
183
|
"""Decoding the packet payload and route it appropriately."""
|
|
226
184
|
|
|
227
|
-
# All methods require a valid
|
|
228
|
-
# requires a valid
|
|
229
|
-
|
|
230
|
-
def detect_array_fragment(this, prev) -> dict: # _PayloadT
|
|
231
|
-
"""Return complete array if this pkt is the latter half of an array."""
|
|
232
|
-
# This will work, even if the 2nd pkt._is_array == False as 1st == True
|
|
233
|
-
# .I --- 01:158182 --:------ 01:158182 000A 048 001201F409C4011101F409C40...
|
|
234
|
-
# .I --- 01:158182 --:------ 01:158182 000A 006 081001F409C4
|
|
235
|
-
if (
|
|
236
|
-
not prev
|
|
237
|
-
or not prev._has_array
|
|
238
|
-
or this.code not in (Code._000A, Code._22C9)
|
|
239
|
-
or this.code != prev.code
|
|
240
|
-
or this.verb != prev.verb != I_
|
|
241
|
-
or this.src != prev.src
|
|
242
|
-
or this.dtm >= prev.dtm + td(seconds=3)
|
|
243
|
-
):
|
|
244
|
-
return this.payload
|
|
245
|
-
|
|
246
|
-
this._pkt._force_has_array()
|
|
247
|
-
payload = this.payload if isinstance(this.payload, list) else [this.payload]
|
|
248
|
-
return prev.payload + payload
|
|
185
|
+
# All methods require msg with a valid payload, except _create_devices_from_addrs(),
|
|
186
|
+
# which requires a valid payload only for 000C.
|
|
249
187
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
msg._payload = detect_array_fragment(msg, prev_msg) # HACK: needs rethinking?
|
|
188
|
+
def logger_xxxx(msg: Message) -> None:
|
|
189
|
+
if _DBG_FORCE_LOG_MESSAGES:
|
|
190
|
+
_LOGGER.warning(msg)
|
|
191
|
+
elif msg.src is not gwy.hgi or (msg.code != Code._PUZZ and msg.verb != RQ):
|
|
192
|
+
_LOGGER.info(msg)
|
|
193
|
+
elif msg.src is not gwy.hgi or msg.verb != RQ:
|
|
194
|
+
_LOGGER.info(msg)
|
|
195
|
+
elif _LOGGER.getEffectiveLevel() == logging.DEBUG:
|
|
196
|
+
_LOGGER.info(msg)
|
|
261
197
|
|
|
262
198
|
try: # validate / dispatch the packet
|
|
263
|
-
|
|
264
|
-
_check_msg_addrs(msg) # ? InvalidAddrSetError
|
|
199
|
+
_check_msg_addrs(msg) # ?InvalidAddrSetError TODO: ?useful at all
|
|
265
200
|
|
|
266
201
|
# TODO: any use in creating a device only if the payload is valid?
|
|
267
|
-
if gwy.config.reduce_processing
|
|
268
|
-
try:
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
202
|
+
if gwy.config.reduce_processing >= DONT_CREATE_ENTITIES:
|
|
203
|
+
logger_xxxx(msg) # return ensures try's else: clause won't be invoked
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
_create_devices_from_addrs(gwy, msg)
|
|
208
|
+
except LookupError as err:
|
|
209
|
+
(_LOGGER.error if _DBG_INCREASE_LOG_LEVELS else _LOGGER.warning)(
|
|
210
|
+
"%s < %s(%s)", msg._pkt, err.__class__.__name__, err
|
|
211
|
+
)
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
_check_src_slug(msg) # ? raise exc.PacketInvalid
|
|
215
|
+
if (
|
|
216
|
+
msg.src._SLUG != DevType.HGI # avoid: msg.src.id != gwy.hgi.id
|
|
217
|
+
and msg.verb != I_
|
|
218
|
+
and msg.dst is not msg.src
|
|
219
|
+
):
|
|
220
|
+
# HGI80 can do what it likes
|
|
221
|
+
# receiving an I isn't currently in the schema & so can't yet be tested
|
|
222
|
+
_check_dst_slug(msg) # ? raise exc.PacketInvalid
|
|
279
223
|
|
|
280
224
|
if gwy.config.reduce_processing >= DONT_UPDATE_ENTITIES:
|
|
225
|
+
logger_xxxx(msg) # return ensures try's else: clause won't be invoked
|
|
281
226
|
return
|
|
282
227
|
|
|
283
228
|
# NOTE: here, msgs are routed only to devices: routing to other entities (i.e.
|
|
284
229
|
# systems, zones, circuits) is done by those devices (e.g. UFC to UfhCircuit)
|
|
285
230
|
|
|
286
|
-
if isinstance(msg.src, Device): #
|
|
287
|
-
msg.src._handle_msg
|
|
231
|
+
if isinstance(msg.src, Device): # type: ignore[unreachable]
|
|
232
|
+
gwy._loop.call_soon(msg.src._handle_msg, msg) # type: ignore[unreachable]
|
|
288
233
|
|
|
289
|
-
# TODO:
|
|
290
|
-
if msg.dst is not msg.src:
|
|
291
|
-
devices = (msg.dst,) # dont: msg.dst._handle_msg(msg)
|
|
234
|
+
# TODO: only be for fully-faked (not Fakable) dst (it picks up via RF if not)
|
|
292
235
|
|
|
293
|
-
|
|
294
|
-
devices =
|
|
236
|
+
if msg.code == Code._1FC9 and msg.payload[SZ_PHASE] == SZ_OFFER:
|
|
237
|
+
devices = [d for d in gwy.devices if d is not msg.src and d._is_binding]
|
|
295
238
|
|
|
296
|
-
elif
|
|
239
|
+
elif msg.dst == ALL_DEV_ADDR: # some offers use dst=63:, so after 1FC9 offer
|
|
240
|
+
devices = [d for d in gwy.devices if d is not msg.src and d.is_faked]
|
|
241
|
+
|
|
242
|
+
elif msg.dst is not msg.src and isinstance(msg.dst, Fakeable): # type: ignore[unreachable]
|
|
243
|
+
# to eavesdrop pkts from other devices, but relevant to this device
|
|
244
|
+
# dont: msg.dst._handle_msg(msg)
|
|
245
|
+
devices = [msg.dst] # type: ignore[unreachable]
|
|
246
|
+
|
|
247
|
+
# TODO: this may not be required...
|
|
248
|
+
elif hasattr(msg.src, SZ_DEVICES): # FIXME: use isinstance()
|
|
249
|
+
# elif isinstance(msg.src, Controller):
|
|
297
250
|
# .I --- 22:060293 --:------ 22:060293 0008 002 000C
|
|
298
251
|
# .I --- 01:054173 --:------ 01:054173 0008 002 03AA
|
|
299
252
|
# needed for (e.g.) faked relays: each device decides if the pkt is useful
|
|
300
|
-
devices = msg.src.devices
|
|
253
|
+
devices = msg.src.devices
|
|
301
254
|
|
|
302
255
|
else:
|
|
303
|
-
|
|
256
|
+
devices = []
|
|
304
257
|
|
|
305
|
-
|
|
258
|
+
for d in devices: # FIXME: some may be Addresses?
|
|
259
|
+
gwy._loop.call_soon(d._handle_msg, msg)
|
|
306
260
|
|
|
307
|
-
except (AssertionError,
|
|
308
|
-
(_LOGGER.error if
|
|
309
|
-
"%s < %s(%s)", msg._pkt,
|
|
261
|
+
except (AssertionError, exc.RamsesException, NotImplementedError) as err:
|
|
262
|
+
(_LOGGER.error if _DBG_INCREASE_LOG_LEVELS else _LOGGER.warning)(
|
|
263
|
+
"%s < %s(%s)", msg._pkt, err.__class__.__name__, err
|
|
310
264
|
)
|
|
311
265
|
|
|
312
|
-
except (AttributeError, LookupError, TypeError, ValueError) as
|
|
313
|
-
_LOGGER.exception("%s < %s(%s)", msg._pkt,
|
|
266
|
+
except (AttributeError, LookupError, TypeError, ValueError) as err:
|
|
267
|
+
_LOGGER.exception("%s < %s(%s)", msg._pkt, err.__class__.__name__, err)
|
|
268
|
+
|
|
269
|
+
else:
|
|
270
|
+
logger_xxxx(msg)
|
|
271
|
+
if gwy._zzz:
|
|
272
|
+
gwy._zzz.add(msg)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# TODO: this needs cleaning up (e.g. handle intervening packet)
|
|
276
|
+
def detect_array_fragment(this: Message, prev: Message) -> bool: # _PayloadT
|
|
277
|
+
"""Return a merged array if this pkt is the latter half of an array."""
|
|
278
|
+
# This will work, even if the 2nd pkt._is_array == False as 1st == True
|
|
279
|
+
# .I --- 01:158182 --:------ 01:158182 000A 048 001201F409C4011101F409C40...
|
|
280
|
+
# .I --- 01:158182 --:------ 01:158182 000A 006 081001F409C4
|
|
281
|
+
|
|
282
|
+
return bool(
|
|
283
|
+
prev._has_array
|
|
284
|
+
and this.code in (Code._000A, Code._22C9) # TODO: not a complete list
|
|
285
|
+
and this.code == prev.code
|
|
286
|
+
and this.verb == prev.verb == I_
|
|
287
|
+
and this.src == prev.src
|
|
288
|
+
and this.dtm < prev.dtm + _TD_SECONDS_003
|
|
289
|
+
)
|