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
|
@@ -1,89 +1,78 @@
|
|
|
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
|
Schema processor for protocol (lower) layer.
|
|
7
5
|
"""
|
|
6
|
+
|
|
8
7
|
from __future__ import annotations
|
|
9
8
|
|
|
10
9
|
import logging
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import Any, Final, Never, NewType, TypeAlias, TypedDict, TypeVar
|
|
12
|
+
|
|
13
|
+
import voluptuous as vol
|
|
14
|
+
|
|
15
|
+
from .const import (
|
|
16
|
+
DEFAULT_ECHO_TIMEOUT,
|
|
17
|
+
DEFAULT_RPLY_TIMEOUT,
|
|
18
|
+
DEV_TYPE_MAP,
|
|
19
|
+
DEVICE_ID_REGEX,
|
|
20
|
+
MAX_DUTY_CYCLE_RATE,
|
|
21
|
+
MIN_INTER_WRITE_GAP,
|
|
22
|
+
)
|
|
18
23
|
|
|
19
24
|
_LOGGER = logging.getLogger(__name__)
|
|
20
|
-
if DEV_MODE:
|
|
21
|
-
_LOGGER.setLevel(logging.DEBUG)
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
#
|
|
25
28
|
# 0/5: Packet source configuration
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"""Return a packet source dict.
|
|
32
|
-
|
|
33
|
-
usage:
|
|
34
|
-
|
|
35
|
-
SCH_PACKET_SOURCE = vol.Schema(
|
|
36
|
-
sch_packet_source_dict_factory(), extra=vol.PREVENT_EXTRA
|
|
37
|
-
)
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
SCH_PACKET_SOURCE_CONFIG = vol.Schema(
|
|
41
|
-
{},
|
|
42
|
-
extra=vol.PREVENT_EXTRA,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
SCH_PACKET_SOURCE_FILE = TextIO
|
|
46
|
-
|
|
47
|
-
def NormalisePacketSource():
|
|
48
|
-
def normalise_packet_source(node_value: str | dict) -> dict:
|
|
49
|
-
if isinstance(node_value, str):
|
|
50
|
-
return {
|
|
51
|
-
SZ_INPUT_FILE: node_value,
|
|
52
|
-
}
|
|
53
|
-
return node_value
|
|
29
|
+
SZ_COMMS_PARAMS: Final = "comms_params"
|
|
30
|
+
SZ_DUTY_CYCLE_LIMIT: Final = "duty_cycle_limit"
|
|
31
|
+
SZ_GAP_BETWEEN_WRITES: Final = "gap_between_writes"
|
|
32
|
+
SZ_ECHO_TIMEOUT: Final = "echo_timeout"
|
|
33
|
+
SZ_RPLY_TIMEOUT: Final = "reply_timeout"
|
|
54
34
|
|
|
55
|
-
|
|
35
|
+
SCH_COMMS_PARAMS = vol.Schema(
|
|
36
|
+
{
|
|
37
|
+
vol.Required(SZ_DUTY_CYCLE_LIMIT, default=MAX_DUTY_CYCLE_RATE): vol.All(
|
|
38
|
+
float, vol.Range(min=0.005, max=0.2)
|
|
39
|
+
),
|
|
40
|
+
vol.Required(SZ_GAP_BETWEEN_WRITES, default=MIN_INTER_WRITE_GAP): vol.All(
|
|
41
|
+
float, vol.Range(min=0.05, max=1.0)
|
|
42
|
+
),
|
|
43
|
+
vol.Required(SZ_ECHO_TIMEOUT, default=DEFAULT_ECHO_TIMEOUT): vol.All(
|
|
44
|
+
float, vol.Range(min=0.01, max=1.0)
|
|
45
|
+
),
|
|
46
|
+
vol.Required(SZ_RPLY_TIMEOUT, default=DEFAULT_RPLY_TIMEOUT): vol.All(
|
|
47
|
+
float, vol.Range(min=0.01, max=1.0)
|
|
48
|
+
),
|
|
49
|
+
},
|
|
50
|
+
extra=vol.PREVENT_EXTRA,
|
|
51
|
+
)
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
SCH_PACKET_SOURCE_FILE,
|
|
62
|
-
NormalisePacketSource(),
|
|
63
|
-
),
|
|
64
|
-
SCH_PACKET_SOURCE_CONFIG.extend(
|
|
65
|
-
{vol.Required(SZ_INPUT_FILE): SCH_PACKET_SOURCE_FILE}
|
|
66
|
-
),
|
|
67
|
-
)
|
|
68
|
-
}
|
|
53
|
+
#
|
|
54
|
+
# 1/5: Packet source configuration
|
|
55
|
+
SZ_INPUT_FILE: Final = "input_file"
|
|
56
|
+
SZ_PACKET_SOURCE: Final = "packet_source"
|
|
69
57
|
|
|
70
58
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
59
|
+
#
|
|
60
|
+
# 2/5: Packet log configuration
|
|
61
|
+
SZ_FILE_NAME: Final = "file_name"
|
|
62
|
+
SZ_PACKET_LOG: Final = "packet_log"
|
|
63
|
+
SZ_ROTATE_BACKUPS: Final = "rotate_backups"
|
|
64
|
+
SZ_ROTATE_BYTES: Final = "rotate_bytes"
|
|
76
65
|
|
|
77
66
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
SZ_ROTATE_BACKUPS = "rotate_backups"
|
|
83
|
-
SZ_ROTATE_BYTES = "rotate_bytes"
|
|
67
|
+
class PktLogConfigT(TypedDict):
|
|
68
|
+
file_name: str
|
|
69
|
+
rotate_backups: int
|
|
70
|
+
rotate_bytes: int | None
|
|
84
71
|
|
|
85
72
|
|
|
86
|
-
def sch_packet_log_dict_factory(
|
|
73
|
+
def sch_packet_log_dict_factory(
|
|
74
|
+
default_backups: int = 0,
|
|
75
|
+
) -> dict[vol.Required, vol.Any]:
|
|
87
76
|
"""Return a packet log dict with a configurable default rotation policy.
|
|
88
77
|
|
|
89
78
|
usage:
|
|
@@ -105,8 +94,8 @@ def sch_packet_log_dict_factory(default_backups=0) -> dict[vol.Required, vol.Any
|
|
|
105
94
|
|
|
106
95
|
SCH_PACKET_LOG_NAME = str
|
|
107
96
|
|
|
108
|
-
def NormalisePacketLog(rotate_backups=0):
|
|
109
|
-
def normalise_packet_log(node_value: str |
|
|
97
|
+
def NormalisePacketLog(rotate_backups: int = 0) -> Callable[..., Any]:
|
|
98
|
+
def normalise_packet_log(node_value: str | PktLogConfigT) -> PktLogConfigT:
|
|
110
99
|
if isinstance(node_value, str):
|
|
111
100
|
return {
|
|
112
101
|
SZ_FILE_NAME: node_value,
|
|
@@ -131,17 +120,21 @@ def sch_packet_log_dict_factory(default_backups=0) -> dict[vol.Required, vol.Any
|
|
|
131
120
|
}
|
|
132
121
|
|
|
133
122
|
|
|
123
|
+
SCH_PACKET_LOG = vol.Schema(
|
|
124
|
+
sch_packet_log_dict_factory(default_backups=7), extra=vol.PREVENT_EXTRA
|
|
125
|
+
)
|
|
126
|
+
|
|
134
127
|
#
|
|
135
|
-
#
|
|
136
|
-
SZ_PORT_CONFIG = "port_config"
|
|
137
|
-
SZ_PORT_NAME = "port_name"
|
|
138
|
-
SZ_SERIAL_PORT = "serial_port"
|
|
128
|
+
# 3/5: Serial port configuration
|
|
129
|
+
SZ_PORT_CONFIG: Final = "port_config"
|
|
130
|
+
SZ_PORT_NAME: Final = "port_name"
|
|
131
|
+
SZ_SERIAL_PORT: Final = "serial_port"
|
|
139
132
|
|
|
140
|
-
SZ_BAUDRATE = "baudrate"
|
|
141
|
-
SZ_DSRDTR = "dsrdtr"
|
|
142
|
-
SZ_RTSCTS = "rtscts"
|
|
143
|
-
SZ_TIMEOUT = "timeout"
|
|
144
|
-
SZ_XONXOFF = "xonxoff"
|
|
133
|
+
SZ_BAUDRATE: Final = "baudrate"
|
|
134
|
+
SZ_DSRDTR: Final = "dsrdtr"
|
|
135
|
+
SZ_RTSCTS: Final = "rtscts"
|
|
136
|
+
SZ_TIMEOUT: Final = "timeout"
|
|
137
|
+
SZ_XONXOFF: Final = "xonxoff"
|
|
145
138
|
|
|
146
139
|
|
|
147
140
|
SCH_SERIAL_PORT_CONFIG = vol.Schema(
|
|
@@ -158,6 +151,14 @@ SCH_SERIAL_PORT_CONFIG = vol.Schema(
|
|
|
158
151
|
)
|
|
159
152
|
|
|
160
153
|
|
|
154
|
+
class PortConfigT(TypedDict):
|
|
155
|
+
baudrate: int # 57600, 115200
|
|
156
|
+
dsrdtr: bool
|
|
157
|
+
rtscts: bool
|
|
158
|
+
timeout: int
|
|
159
|
+
xonxoff: bool
|
|
160
|
+
|
|
161
|
+
|
|
161
162
|
def sch_serial_port_dict_factory() -> dict[vol.Required, vol.Any]:
|
|
162
163
|
"""Return a serial port dict.
|
|
163
164
|
|
|
@@ -170,10 +171,10 @@ def sch_serial_port_dict_factory() -> dict[vol.Required, vol.Any]:
|
|
|
170
171
|
|
|
171
172
|
SCH_SERIAL_PORT_NAME = str
|
|
172
173
|
|
|
173
|
-
def NormaliseSerialPort():
|
|
174
|
-
def normalise_serial_port(node_value: str |
|
|
174
|
+
def NormaliseSerialPort() -> Callable[[str | PortConfigT], PortConfigT]:
|
|
175
|
+
def normalise_serial_port(node_value: str | PortConfigT) -> PortConfigT:
|
|
175
176
|
if isinstance(node_value, str):
|
|
176
|
-
return {SZ_PORT_NAME: node_value} | SCH_SERIAL_PORT_CONFIG({})
|
|
177
|
+
return {SZ_PORT_NAME: node_value} | SCH_SERIAL_PORT_CONFIG({}) # type: ignore[no-any-return]
|
|
177
178
|
return node_value
|
|
178
179
|
|
|
179
180
|
return normalise_serial_port
|
|
@@ -191,17 +192,21 @@ def sch_serial_port_dict_factory() -> dict[vol.Required, vol.Any]:
|
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
|
|
194
|
-
def extract_serial_port(ser_port_dict: dict) -> tuple[str,
|
|
195
|
+
def extract_serial_port(ser_port_dict: dict[str, Any]) -> tuple[str, PortConfigT]:
|
|
195
196
|
"""Extract a serial port, port_config_dict tuple from a sch_serial_port_dict."""
|
|
196
|
-
port_name = ser_port_dict.get(SZ_PORT_NAME)
|
|
197
|
+
port_name: str = ser_port_dict.get(SZ_PORT_NAME) # type: ignore[assignment]
|
|
197
198
|
port_config = {k: v for k, v in ser_port_dict.items() if k != SZ_PORT_NAME}
|
|
198
|
-
return port_name, port_config
|
|
199
|
+
return port_name, port_config # type: ignore[return-value]
|
|
199
200
|
|
|
200
201
|
|
|
201
202
|
#
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
# 4/5: Traits (of devices) configuration (basic)
|
|
204
|
+
|
|
205
|
+
_T = TypeVar("_T")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def ConvertNullToDict() -> Callable[[_T | None], _T | dict[Never, Never]]:
|
|
209
|
+
def convert_null_to_dict(node_value: _T | None) -> _T | dict[Never, Never]:
|
|
205
210
|
if node_value is None:
|
|
206
211
|
return {}
|
|
207
212
|
return node_value
|
|
@@ -209,12 +214,13 @@ def ConvertNullToDict():
|
|
|
209
214
|
return convert_null_to_dict
|
|
210
215
|
|
|
211
216
|
|
|
212
|
-
SZ_ALIAS = "alias"
|
|
213
|
-
SZ_CLASS = "class"
|
|
214
|
-
SZ_FAKED = "faked"
|
|
217
|
+
SZ_ALIAS: Final = "alias"
|
|
218
|
+
SZ_CLASS: Final = "class"
|
|
219
|
+
SZ_FAKED: Final = "faked"
|
|
220
|
+
SZ_SCHEME: Final = "scheme"
|
|
215
221
|
|
|
216
|
-
SZ_BLOCK_LIST = "block_list"
|
|
217
|
-
SZ_KNOWN_LIST = "known_list"
|
|
222
|
+
SZ_BLOCK_LIST: Final = "block_list"
|
|
223
|
+
SZ_KNOWN_LIST: Final = "known_list"
|
|
218
224
|
|
|
219
225
|
SCH_DEVICE_ID_ANY = vol.Match(DEVICE_ID_REGEX.ANY)
|
|
220
226
|
SCH_DEVICE_ID_SEN = vol.Match(DEVICE_ID_REGEX.SEN)
|
|
@@ -226,13 +232,23 @@ SCH_DEVICE_ID_BDR = vol.Match(DEVICE_ID_REGEX.BDR)
|
|
|
226
232
|
SCH_DEVICE_ID_UFC = vol.Match(DEVICE_ID_REGEX.UFC)
|
|
227
233
|
|
|
228
234
|
_SCH_TRAITS_DOMAINS = ("heat", "hvac")
|
|
229
|
-
_SCH_TRAITS_HVAC_SCHEMES = ("itho", "nuaire", "orcon")
|
|
235
|
+
_SCH_TRAITS_HVAC_SCHEMES = ("itho", "nuaire", "orcon", "vasco", "climarad")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
DeviceTraitsT = TypedDict(
|
|
239
|
+
"DeviceTraitsT",
|
|
240
|
+
{
|
|
241
|
+
"alias": str | None,
|
|
242
|
+
"faked": bool | None,
|
|
243
|
+
"class": str | None,
|
|
244
|
+
},
|
|
245
|
+
)
|
|
230
246
|
|
|
231
247
|
|
|
232
248
|
def sch_global_traits_dict_factory(
|
|
233
|
-
heat_traits: dict[vol.Optional, vol.Any] = None,
|
|
234
|
-
hvac_traits: dict[vol.Optional, vol.Any] = None,
|
|
235
|
-
) -> tuple[dict[vol.Optional, vol.Any], vol.
|
|
249
|
+
heat_traits: dict[vol.Optional, vol.Any] | None = None,
|
|
250
|
+
hvac_traits: dict[vol.Optional, vol.Any] | None = None,
|
|
251
|
+
) -> tuple[dict[vol.Optional, vol.Any], vol.Any]:
|
|
236
252
|
"""Return a global traits dict with a configurable extra traits.
|
|
237
253
|
|
|
238
254
|
usage:
|
|
@@ -254,15 +270,16 @@ def sch_global_traits_dict_factory(
|
|
|
254
270
|
extra=vol.PREVENT_EXTRA,
|
|
255
271
|
)
|
|
256
272
|
|
|
273
|
+
# NOTE: voluptuous doesn't like StrEnums, hence str(s)
|
|
257
274
|
# TIP: the _domain key can be used to force which traits schema to use
|
|
258
275
|
heat_slugs = list(
|
|
259
|
-
s for s in DEV_TYPE_MAP.slugs() if s not in DEV_TYPE_MAP.HVAC_SLUGS
|
|
276
|
+
str(s) for s in DEV_TYPE_MAP.slugs() if s not in DEV_TYPE_MAP.HVAC_SLUGS
|
|
260
277
|
)
|
|
261
278
|
SCH_TRAITS_HEAT = SCH_TRAITS_BASE.extend(
|
|
262
279
|
{
|
|
263
280
|
vol.Optional("_domain", default="heat"): "heat",
|
|
264
281
|
vol.Optional(SZ_CLASS): vol.Any(
|
|
265
|
-
None, *heat_slugs, *(DEV_TYPE_MAP[s] for s in heat_slugs)
|
|
282
|
+
None, *heat_slugs, *(str(DEV_TYPE_MAP[s]) for s in heat_slugs)
|
|
266
283
|
),
|
|
267
284
|
}
|
|
268
285
|
)
|
|
@@ -271,17 +288,18 @@ def sch_global_traits_dict_factory(
|
|
|
271
288
|
extra=vol.PREVENT_EXTRA if heat_traits else vol.REMOVE_EXTRA,
|
|
272
289
|
)
|
|
273
290
|
|
|
274
|
-
|
|
291
|
+
# NOTE: voluptuous doesn't like StrEnums, hence str(s)
|
|
292
|
+
hvac_slugs = list(str(s) for s in DEV_TYPE_MAP.HVAC_SLUGS)
|
|
275
293
|
SCH_TRAITS_HVAC = SCH_TRAITS_BASE.extend(
|
|
276
294
|
{
|
|
277
295
|
vol.Optional("_domain", default="hvac"): "hvac",
|
|
278
296
|
vol.Optional(SZ_CLASS, default="HVC"): vol.Any(
|
|
279
|
-
None, *hvac_slugs, *(DEV_TYPE_MAP[s] for s in hvac_slugs)
|
|
297
|
+
None, *hvac_slugs, *(str(DEV_TYPE_MAP[s]) for s in hvac_slugs)
|
|
280
298
|
), # TODO: consider removing None
|
|
281
299
|
}
|
|
282
300
|
)
|
|
283
301
|
SCH_TRAITS_HVAC = SCH_TRAITS_HVAC.extend(
|
|
284
|
-
{vol.Optional(
|
|
302
|
+
{vol.Optional(SZ_SCHEME): vol.Any(*_SCH_TRAITS_HVAC_SCHEMES)}
|
|
285
303
|
)
|
|
286
304
|
SCH_TRAITS_HVAC = SCH_TRAITS_HVAC.extend(
|
|
287
305
|
hvac_traits,
|
|
@@ -318,84 +336,83 @@ SCH_GLOBAL_TRAITS_DICT, SCH_TRAITS = sch_global_traits_dict_factory()
|
|
|
318
336
|
# Device lists (Engine configuration)
|
|
319
337
|
|
|
320
338
|
|
|
339
|
+
DeviceIdT = NewType("DeviceIdT", str) # TypeVar('DeviceIdT', bound=str) #
|
|
340
|
+
DevIndexT = NewType("DevIndexT", str)
|
|
341
|
+
DeviceListT: TypeAlias = dict[DeviceIdT, DeviceTraitsT]
|
|
342
|
+
|
|
343
|
+
|
|
321
344
|
def select_device_filter_mode(
|
|
322
|
-
enforce_known_list: bool,
|
|
345
|
+
enforce_known_list: bool,
|
|
346
|
+
known_list: DeviceListT,
|
|
347
|
+
block_list: DeviceListT,
|
|
323
348
|
) -> bool:
|
|
324
349
|
"""Determine which device filter to use, if any.
|
|
325
350
|
|
|
326
351
|
Either:
|
|
352
|
+
- block if device_id in block_list (could be empty), otherwise
|
|
327
353
|
- allow if device_id in known_list, or
|
|
328
|
-
- block if device_id in block_list (could be empty)
|
|
329
354
|
"""
|
|
330
355
|
|
|
331
|
-
if
|
|
332
|
-
raise ValueError(
|
|
333
|
-
f"There are devices in both the {SZ_KNOWN_LIST} & {SZ_BLOCK_LIST}: {both}"
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
hgi_list = [
|
|
337
|
-
k
|
|
338
|
-
for k, v in known_list.items()
|
|
339
|
-
if k[:2] == DEV_TYPE_MAP._hex(DEV_TYPE.HGI)
|
|
340
|
-
and v.get(SZ_CLASS) in (None, DEV_TYPE.HGI, DEV_TYPE_MAP[DEV_TYPE.HGI])
|
|
341
|
-
]
|
|
342
|
-
if len(hgi_list) != 1:
|
|
343
|
-
_LOGGER.warning(
|
|
344
|
-
f"Best practice is exactly one gateway (HGI) in the {SZ_KNOWN_LIST}: %s",
|
|
345
|
-
hgi_list,
|
|
346
|
-
)
|
|
356
|
+
# warn if not has_exactly_one_valid_hgi(known_list)
|
|
347
357
|
|
|
348
358
|
if enforce_known_list and not known_list:
|
|
349
359
|
_LOGGER.warning(
|
|
350
|
-
f"
|
|
351
|
-
f"
|
|
360
|
+
f"Best practice is to enforce a {SZ_KNOWN_LIST} (an allow list), "
|
|
361
|
+
f"but it is empty, so it can't be used "
|
|
352
362
|
)
|
|
353
363
|
enforce_known_list = False
|
|
354
364
|
|
|
355
365
|
if enforce_known_list:
|
|
356
366
|
_LOGGER.info(
|
|
357
|
-
f"
|
|
358
|
-
f"as a
|
|
367
|
+
f"A valid {SZ_KNOWN_LIST} was provided, "
|
|
368
|
+
f"and will be enforced as a allow list: length = {len(known_list)}"
|
|
359
369
|
)
|
|
360
370
|
_LOGGER.debug(f"known_list = {known_list}")
|
|
361
371
|
|
|
362
372
|
elif block_list:
|
|
363
373
|
_LOGGER.info(
|
|
364
|
-
f"
|
|
365
|
-
f"as a
|
|
374
|
+
f"A valid {SZ_BLOCK_LIST} was provided, "
|
|
375
|
+
f"and will be used as a deny list: length = {len(block_list)}"
|
|
366
376
|
)
|
|
367
377
|
_LOGGER.debug(f"block_list = {block_list}")
|
|
368
378
|
|
|
369
379
|
elif known_list:
|
|
370
380
|
_LOGGER.warning(
|
|
371
|
-
f"
|
|
372
|
-
f"
|
|
381
|
+
f"Best practice is to enforce the {SZ_KNOWN_LIST} as an allow list, "
|
|
382
|
+
f"configure: {SZ_ENFORCE_KNOWN_LIST} = True"
|
|
373
383
|
)
|
|
374
384
|
_LOGGER.debug(f"known_list = {known_list}")
|
|
375
385
|
|
|
376
386
|
else:
|
|
377
387
|
_LOGGER.warning(
|
|
378
|
-
f"
|
|
379
|
-
f"
|
|
388
|
+
f"Best practice is to provide a {SZ_KNOWN_LIST} and enforce it, "
|
|
389
|
+
f"configure: {SZ_ENFORCE_KNOWN_LIST} = True"
|
|
380
390
|
)
|
|
381
391
|
|
|
382
392
|
return enforce_known_list
|
|
383
393
|
|
|
384
394
|
|
|
385
395
|
#
|
|
386
|
-
#
|
|
387
|
-
SZ_DISABLE_SENDING = "disable_sending"
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
396
|
+
# 5/5: Gateway (engine) configuration
|
|
397
|
+
SZ_DISABLE_SENDING: Final = "disable_sending"
|
|
398
|
+
SZ_DISABLE_QOS: Final = "disable_qos"
|
|
399
|
+
SZ_ENFORCE_KNOWN_LIST: Final[str] = f"enforce_{SZ_KNOWN_LIST}"
|
|
400
|
+
SZ_EVOFW_FLAG: Final = "evofw_flag"
|
|
401
|
+
SZ_USE_REGEX: Final = "use_regex"
|
|
391
402
|
|
|
392
403
|
SCH_ENGINE_DICT = {
|
|
393
404
|
vol.Optional(SZ_DISABLE_SENDING, default=False): bool,
|
|
405
|
+
vol.Optional(SZ_DISABLE_QOS, default=None): vol.Any(
|
|
406
|
+
None, # None is selective QoS (e.g. QoS only for bindings, schedule, etc.)
|
|
407
|
+
bool,
|
|
408
|
+
), # in long term, this default to be True (and no None)
|
|
394
409
|
vol.Optional(SZ_ENFORCE_KNOWN_LIST, default=False): bool,
|
|
395
410
|
vol.Optional(SZ_EVOFW_FLAG): vol.Any(None, str),
|
|
396
411
|
# vol.Optional(SZ_PORT_CONFIG): SCH_SERIAL_PORT_CONFIG,
|
|
397
412
|
vol.Optional(SZ_USE_REGEX): dict, # vol.All(ConvertNullToDict(), dict),
|
|
413
|
+
vol.Optional(SZ_COMMS_PARAMS): SCH_COMMS_PARAMS,
|
|
398
414
|
}
|
|
415
|
+
SCH_ENGINE_CONFIG = vol.Schema(SCH_ENGINE_DICT, extra=vol.REMOVE_EXTRA)
|
|
399
416
|
|
|
400
|
-
SZ_INBOUND = "inbound" # for use_regex (intentionally obscured)
|
|
401
|
-
SZ_OUTBOUND = "outbound"
|
|
417
|
+
SZ_INBOUND: Final = "inbound" # for use_regex (intentionally obscured)
|
|
418
|
+
SZ_OUTBOUND: Final = "outbound"
|