conson-xp 1.0.1__py3-none-any.whl → 1.2.0__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.
- {conson_xp-1.0.1.dist-info → conson_xp-1.2.0.dist-info}/METADATA +1 -1
- conson_xp-1.2.0.dist-info/RECORD +181 -0
- xp/__init__.py +4 -3
- xp/api/main.py +18 -3
- xp/api/models/api.py +13 -2
- xp/api/models/discover.py +12 -2
- xp/api/routers/conbus_blink.py +18 -6
- xp/api/routers/conbus_custom.py +11 -3
- xp/api/routers/conbus_datapoint.py +10 -3
- xp/api/routers/conbus_output.py +29 -9
- xp/api/routers/errors.py +6 -5
- xp/cli/__init__.py +1 -1
- xp/cli/commands/__init__.py +1 -0
- xp/cli/commands/api.py +1 -5
- xp/cli/commands/api_start_commands.py +14 -8
- xp/cli/commands/conbus/conbus.py +9 -37
- xp/cli/commands/conbus/conbus_actiontable_commands.py +21 -1
- xp/cli/commands/conbus/conbus_autoreport_commands.py +21 -11
- xp/cli/commands/conbus/conbus_blink_commands.py +53 -21
- xp/cli/commands/conbus/conbus_config_commands.py +7 -4
- xp/cli/commands/conbus/conbus_custom_commands.py +13 -4
- xp/cli/commands/conbus/conbus_datapoint_commands.py +28 -8
- xp/cli/commands/conbus/conbus_discover_commands.py +15 -4
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +50 -11
- xp/cli/commands/conbus/conbus_linknumber_commands.py +21 -11
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +25 -1
- xp/cli/commands/conbus/conbus_output_commands.py +46 -12
- xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
- xp/cli/commands/conbus/conbus_receive_commands.py +15 -7
- xp/cli/commands/conbus/conbus_scan_commands.py +35 -102
- xp/cli/commands/file_commands.py +26 -15
- xp/cli/commands/homekit/homekit.py +14 -8
- xp/cli/commands/homekit/homekit_start_commands.py +5 -5
- xp/cli/commands/module_commands.py +26 -19
- xp/cli/commands/reverse_proxy_commands.py +24 -18
- xp/cli/commands/server/server_commands.py +18 -18
- xp/cli/commands/telegram/telegram.py +4 -12
- xp/cli/commands/telegram/telegram_blink_commands.py +10 -8
- xp/cli/commands/telegram/telegram_checksum_commands.py +19 -8
- xp/cli/commands/telegram/telegram_discover_commands.py +2 -4
- xp/cli/commands/telegram/telegram_linknumber_commands.py +11 -8
- xp/cli/commands/telegram/telegram_parse_commands.py +10 -9
- xp/cli/commands/telegram/telegram_version_commands.py +8 -4
- xp/cli/main.py +5 -1
- xp/cli/utils/click_tree.py +23 -3
- xp/cli/utils/datapoint_type_choice.py +20 -0
- xp/cli/utils/decorators.py +165 -14
- xp/cli/utils/error_handlers.py +49 -18
- xp/cli/utils/formatters.py +95 -10
- xp/cli/utils/serial_number_type.py +18 -0
- xp/cli/utils/system_function_choice.py +20 -0
- xp/cli/utils/xp_module_type.py +20 -0
- xp/connection/__init__.py +1 -1
- xp/connection/exceptions.py +5 -5
- xp/models/__init__.py +1 -1
- xp/models/actiontable/__init__.py +1 -0
- xp/models/actiontable/actiontable.py +17 -1
- xp/models/actiontable/msactiontable_xp20.py +10 -0
- xp/models/actiontable/msactiontable_xp24.py +20 -3
- xp/models/actiontable/msactiontable_xp33.py +27 -4
- xp/models/conbus/__init__.py +1 -0
- xp/models/conbus/conbus.py +34 -4
- xp/models/conbus/conbus_autoreport.py +20 -2
- xp/models/conbus/conbus_blink.py +22 -2
- xp/models/conbus/conbus_client_config.py +22 -1
- xp/models/conbus/conbus_connection_status.py +16 -2
- xp/models/conbus/conbus_custom.py +21 -2
- xp/models/conbus/conbus_datapoint.py +22 -2
- xp/models/conbus/conbus_discover.py +18 -2
- xp/models/conbus/conbus_lightlevel.py +20 -2
- xp/models/conbus/conbus_linknumber.py +20 -2
- xp/models/conbus/conbus_output.py +22 -2
- xp/models/conbus/conbus_raw.py +17 -2
- xp/models/conbus/conbus_receive.py +16 -2
- xp/models/homekit/__init__.py +1 -0
- xp/models/homekit/homekit_accessory.py +15 -1
- xp/models/homekit/homekit_config.py +52 -0
- xp/models/homekit/homekit_conson_config.py +32 -0
- xp/models/log_entry.py +49 -9
- xp/models/protocol/__init__.py +1 -0
- xp/models/protocol/conbus_protocol.py +130 -21
- xp/models/telegram/__init__.py +1 -0
- xp/models/telegram/action_type.py +16 -2
- xp/models/telegram/datapoint_type.py +36 -2
- xp/models/telegram/event_telegram.py +46 -10
- xp/models/telegram/event_type.py +8 -1
- xp/models/telegram/input_action_type.py +34 -2
- xp/models/telegram/input_type.py +9 -1
- xp/models/telegram/module_type.py +69 -19
- xp/models/telegram/module_type_code.py +43 -1
- xp/models/telegram/output_telegram.py +30 -6
- xp/models/telegram/reply_telegram.py +56 -11
- xp/models/telegram/system_function.py +35 -3
- xp/models/telegram/system_telegram.py +18 -4
- xp/models/telegram/telegram.py +12 -3
- xp/models/telegram/telegram_type.py +8 -1
- xp/models/telegram/timeparam_type.py +27 -0
- xp/models/write_config_type.py +17 -2
- xp/services/__init__.py +1 -1
- xp/services/conbus/__init__.py +1 -0
- xp/services/conbus/actiontable/__init__.py +1 -0
- xp/services/conbus/actiontable/actiontable_service.py +33 -2
- xp/services/conbus/actiontable/msactiontable_service.py +40 -3
- xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +36 -4
- xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +45 -5
- xp/services/conbus/conbus_autoreport_get_service.py +17 -8
- xp/services/conbus/conbus_autoreport_set_service.py +29 -16
- xp/services/conbus/conbus_blink_all_service.py +40 -21
- xp/services/conbus/conbus_blink_service.py +37 -13
- xp/services/conbus/conbus_custom_service.py +29 -13
- xp/services/conbus/conbus_datapoint_queryall_service.py +40 -16
- xp/services/conbus/conbus_datapoint_service.py +33 -12
- xp/services/conbus/conbus_discover_service.py +43 -7
- xp/services/conbus/conbus_lightlevel_get_service.py +22 -14
- xp/services/conbus/conbus_lightlevel_set_service.py +40 -20
- xp/services/conbus/conbus_linknumber_get_service.py +18 -10
- xp/services/conbus/conbus_linknumber_set_service.py +34 -8
- xp/services/conbus/conbus_output_service.py +33 -13
- xp/services/conbus/conbus_raw_service.py +36 -16
- xp/services/conbus/conbus_receive_service.py +38 -6
- xp/services/conbus/conbus_scan_service.py +45 -19
- xp/services/homekit/__init__.py +1 -0
- xp/services/homekit/homekit_cache_service.py +31 -6
- xp/services/homekit/homekit_conbus_service.py +33 -2
- xp/services/homekit/homekit_config_validator.py +97 -15
- xp/services/homekit/homekit_conson_validator.py +51 -7
- xp/services/homekit/homekit_dimminglight.py +47 -1
- xp/services/homekit/homekit_dimminglight_service.py +35 -1
- xp/services/homekit/homekit_hap_service.py +71 -18
- xp/services/homekit/homekit_lightbulb.py +35 -1
- xp/services/homekit/homekit_lightbulb_service.py +30 -2
- xp/services/homekit/homekit_module_service.py +23 -1
- xp/services/homekit/homekit_outlet.py +47 -1
- xp/services/homekit/homekit_outlet_service.py +44 -2
- xp/services/homekit/homekit_service.py +113 -19
- xp/services/log_file_service.py +37 -41
- xp/services/module_type_service.py +26 -5
- xp/services/protocol/__init__.py +1 -1
- xp/services/protocol/conbus_protocol.py +115 -20
- xp/services/protocol/protocol_factory.py +40 -0
- xp/services/protocol/telegram_protocol.py +38 -7
- xp/services/reverse_proxy_service.py +79 -14
- xp/services/server/__init__.py +1 -0
- xp/services/server/base_server_service.py +102 -14
- xp/services/server/cp20_server_service.py +12 -4
- xp/services/server/server_service.py +26 -11
- xp/services/server/xp130_server_service.py +11 -3
- xp/services/server/xp20_server_service.py +11 -3
- xp/services/server/xp230_server_service.py +11 -3
- xp/services/server/xp24_server_service.py +33 -6
- xp/services/server/xp33_server_service.py +41 -8
- xp/services/telegram/__init__.py +1 -0
- xp/services/telegram/telegram_blink_service.py +19 -31
- xp/services/telegram/telegram_checksum_service.py +10 -10
- xp/services/telegram/telegram_discover_service.py +58 -29
- xp/services/telegram/telegram_link_number_service.py +27 -40
- xp/services/telegram/telegram_output_service.py +46 -49
- xp/services/telegram/telegram_service.py +41 -41
- xp/services/telegram/telegram_version_service.py +4 -2
- xp/utils/__init__.py +1 -1
- xp/utils/dependencies.py +0 -1
- xp/utils/serialization.py +6 -0
- xp/utils/time_utils.py +6 -11
- conson_xp-1.0.1.dist-info/RECORD +0 -181
- {conson_xp-1.0.1.dist-info → conson_xp-1.2.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.0.1.dist-info → conson_xp-1.2.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.0.1.dist-info → conson_xp-1.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Conbus Scan Service for TCP communication with Conbus servers.
|
|
2
2
|
|
|
3
|
-
This service implements a TCP client that
|
|
4
|
-
|
|
3
|
+
This service implements a TCP client that scans Conbus servers and sends
|
|
4
|
+
telegrams to scan modules for all datapoints by function code.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import logging
|
|
@@ -20,10 +20,10 @@ from xp.services.protocol import ConbusProtocol
|
|
|
20
20
|
|
|
21
21
|
class ConbusScanService(ConbusProtocol):
|
|
22
22
|
"""
|
|
23
|
-
Service for
|
|
23
|
+
Service for scanning modules for all datapoints by function code.
|
|
24
24
|
|
|
25
|
-
Uses ConbusProtocol to provide
|
|
26
|
-
|
|
25
|
+
Uses ConbusProtocol to provide scan functionality for discovering
|
|
26
|
+
all available datapoints on a module.
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
def __init__(
|
|
@@ -31,7 +31,12 @@ class ConbusScanService(ConbusProtocol):
|
|
|
31
31
|
cli_config: ConbusClientConfig,
|
|
32
32
|
reactor: PosixReactorBase,
|
|
33
33
|
) -> None:
|
|
34
|
-
"""Initialize the Conbus
|
|
34
|
+
"""Initialize the Conbus scan service.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
cli_config: Conbus client configuration.
|
|
38
|
+
reactor: Twisted reactor instance.
|
|
39
|
+
"""
|
|
35
40
|
super().__init__(cli_config, reactor)
|
|
36
41
|
self.serial_number: str = ""
|
|
37
42
|
self.function_code: str = ""
|
|
@@ -49,10 +54,16 @@ class ConbusScanService(ConbusProtocol):
|
|
|
49
54
|
self.logger = logging.getLogger(__name__)
|
|
50
55
|
|
|
51
56
|
def connection_established(self) -> None:
|
|
57
|
+
"""Handle connection established event."""
|
|
52
58
|
self.logger.debug("Connection established, starting scan")
|
|
53
59
|
self.scan_next_datacode()
|
|
54
60
|
|
|
55
61
|
def scan_next_datacode(self) -> bool:
|
|
62
|
+
"""Scan the next data code.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if scanning should continue, False if complete.
|
|
66
|
+
"""
|
|
56
67
|
self.datapoint_value += 1
|
|
57
68
|
if self.datapoint_value >= 100:
|
|
58
69
|
if self.finish_callback:
|
|
@@ -66,10 +77,20 @@ class ConbusScanService(ConbusProtocol):
|
|
|
66
77
|
return True
|
|
67
78
|
|
|
68
79
|
def telegram_sent(self, telegram_sent: str) -> None:
|
|
80
|
+
"""Handle telegram sent event.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
telegram_sent: The telegram that was sent.
|
|
84
|
+
"""
|
|
69
85
|
self.service_response.success = True
|
|
70
86
|
self.service_response.sent_telegrams.append(telegram_sent)
|
|
71
87
|
|
|
72
88
|
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
89
|
+
"""Handle telegram received event.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
telegram_received: The telegram received event.
|
|
93
|
+
"""
|
|
73
94
|
self.logger.debug(f"Telegram received: {telegram_received}")
|
|
74
95
|
if not self.service_response.received_telegrams:
|
|
75
96
|
self.service_response.received_telegrams = []
|
|
@@ -79,11 +100,21 @@ class ConbusScanService(ConbusProtocol):
|
|
|
79
100
|
self.progress_callback(telegram_received.frame)
|
|
80
101
|
|
|
81
102
|
def timeout(self) -> bool:
|
|
103
|
+
"""Handle timeout event by scanning next data code.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True to continue scanning, False to stop.
|
|
107
|
+
"""
|
|
82
108
|
self.logger.debug(f"Timeout: {self.timeout_seconds}s")
|
|
83
109
|
continue_scan = self.scan_next_datacode()
|
|
84
110
|
return continue_scan
|
|
85
111
|
|
|
86
112
|
def failed(self, message: str) -> None:
|
|
113
|
+
"""Handle failed connection event.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
message: Failure message.
|
|
117
|
+
"""
|
|
87
118
|
self.logger.debug(f"Failed with message: {message}")
|
|
88
119
|
self.service_response.success = False
|
|
89
120
|
self.service_response.timestamp = datetime.now()
|
|
@@ -91,7 +122,7 @@ class ConbusScanService(ConbusProtocol):
|
|
|
91
122
|
if self.finish_callback:
|
|
92
123
|
self.finish_callback(self.service_response)
|
|
93
124
|
|
|
94
|
-
def
|
|
125
|
+
def scan_module(
|
|
95
126
|
self,
|
|
96
127
|
serial_number: str,
|
|
97
128
|
function_code: str,
|
|
@@ -99,21 +130,16 @@ class ConbusScanService(ConbusProtocol):
|
|
|
99
130
|
finish_callback: Callable[[ConbusResponse], None],
|
|
100
131
|
timeout_seconds: Optional[float] = None,
|
|
101
132
|
) -> None:
|
|
102
|
-
"""
|
|
103
|
-
Query a specific datapoint from a module.
|
|
133
|
+
"""Scan a module for all datapoints by function code.
|
|
104
134
|
|
|
105
135
|
Args:
|
|
106
|
-
serial_number: 10-digit module serial number
|
|
107
|
-
function_code:
|
|
108
|
-
progress_callback:
|
|
109
|
-
finish_callback:
|
|
110
|
-
timeout_seconds:
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
ConbusDatapointResponse with operation result and datapoint value
|
|
136
|
+
serial_number: 10-digit module serial number.
|
|
137
|
+
function_code: The function code to scan.
|
|
138
|
+
progress_callback: Callback to handle progress.
|
|
139
|
+
finish_callback: Callback function to call when the scan is complete.
|
|
140
|
+
timeout_seconds: Timeout in seconds.
|
|
114
141
|
"""
|
|
115
|
-
|
|
116
|
-
self.logger.info("Starting query_datapoint")
|
|
142
|
+
self.logger.info("Starting scan_module")
|
|
117
143
|
if timeout_seconds:
|
|
118
144
|
self.timeout_seconds = timeout_seconds
|
|
119
145
|
|
xp/services/homekit/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""HomeKit integration services."""
|
|
@@ -22,7 +22,12 @@ CACHE_FILE = CACHE_DIR / "homekit_cache.json"
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class CacheEntry(TypedDict):
|
|
25
|
-
"""Cache entry type definition.
|
|
25
|
+
"""Cache entry type definition.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
event: The cached event (OutputStateReceivedEvent or LightLevelReceivedEvent).
|
|
29
|
+
timestamp: When the event was cached.
|
|
30
|
+
"""
|
|
26
31
|
|
|
27
32
|
event: Union[OutputStateReceivedEvent, LightLevelReceivedEvent]
|
|
28
33
|
timestamp: datetime
|
|
@@ -39,6 +44,12 @@ class HomeKitCacheService:
|
|
|
39
44
|
"""
|
|
40
45
|
|
|
41
46
|
def __init__(self, event_bus: EventBus, enable_persistence: bool = True):
|
|
47
|
+
"""Initialize the HomeKit cache service.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
event_bus: Event bus for inter-service communication.
|
|
51
|
+
enable_persistence: Whether to persist cache to disk.
|
|
52
|
+
"""
|
|
42
53
|
self.logger = logging.getLogger(__name__)
|
|
43
54
|
self.event_bus = event_bus
|
|
44
55
|
self.cache: dict[tuple[str, DataPointType], CacheEntry] = {}
|
|
@@ -192,12 +203,14 @@ class HomeKitCacheService:
|
|
|
192
203
|
return None
|
|
193
204
|
|
|
194
205
|
def handle_read_datapoint_event(self, event: ReadDatapointEvent) -> None:
|
|
195
|
-
"""
|
|
196
|
-
Handle ReadDatapointEvent by checking cache or refresh flag.
|
|
206
|
+
"""Handle ReadDatapointEvent by checking cache or refresh flag.
|
|
197
207
|
|
|
198
208
|
On refresh_cache=True: invalidate cache and force protocol query
|
|
199
209
|
On cache hit: dispatch cached response event
|
|
200
210
|
On cache miss: forward to protocol via ReadDatapointFromProtocolEvent
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
event: Read datapoint event with serial number, datapoint type, and refresh flag.
|
|
201
214
|
"""
|
|
202
215
|
self.logger.debug(
|
|
203
216
|
f"Handling ReadDatapointEvent: "
|
|
@@ -250,7 +263,11 @@ class HomeKitCacheService:
|
|
|
250
263
|
def handle_output_state_received_event(
|
|
251
264
|
self, event: OutputStateReceivedEvent
|
|
252
265
|
) -> None:
|
|
253
|
-
"""Cache OutputStateReceivedEvent for future queries.
|
|
266
|
+
"""Cache OutputStateReceivedEvent for future queries.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
event: Output state received event to cache.
|
|
270
|
+
"""
|
|
254
271
|
self.logger.debug(
|
|
255
272
|
f"Caching OutputStateReceivedEvent: "
|
|
256
273
|
f"serial={event.serial_number}, "
|
|
@@ -260,7 +277,11 @@ class HomeKitCacheService:
|
|
|
260
277
|
self._cache_event(event)
|
|
261
278
|
|
|
262
279
|
def handle_light_level_received_event(self, event: LightLevelReceivedEvent) -> None:
|
|
263
|
-
"""Cache LightLevelReceivedEvent for future queries.
|
|
280
|
+
"""Cache LightLevelReceivedEvent for future queries.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
event: Light level received event to cache.
|
|
284
|
+
"""
|
|
264
285
|
self.logger.debug(
|
|
265
286
|
f"Caching LightLevelReceivedEvent: "
|
|
266
287
|
f"serial={event.serial_number}, "
|
|
@@ -276,7 +297,11 @@ class HomeKitCacheService:
|
|
|
276
297
|
self._save_cache()
|
|
277
298
|
|
|
278
299
|
def get_cache_stats(self) -> dict[str, int]:
|
|
279
|
-
"""Get cache statistics.
|
|
300
|
+
"""Get cache statistics.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Dictionary with cache statistics including total_entries.
|
|
304
|
+
"""
|
|
280
305
|
return {
|
|
281
306
|
"total_entries": len(self.cache),
|
|
282
307
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""HomeKit Conbus Service for protocol communication.
|
|
2
|
+
|
|
3
|
+
This module bridges HomeKit events with the Conbus protocol for device control.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
import logging
|
|
2
7
|
|
|
3
8
|
from bubus import EventBus
|
|
@@ -14,11 +19,23 @@ from xp.services.protocol.telegram_protocol import TelegramProtocol
|
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class HomeKitConbusService:
|
|
17
|
-
"""
|
|
22
|
+
"""Service for bridging HomeKit events with Conbus protocol.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
event_bus: Event bus for inter-service communication.
|
|
26
|
+
telegram_protocol: Protocol for sending telegrams.
|
|
27
|
+
logger: Logger instance.
|
|
28
|
+
"""
|
|
18
29
|
|
|
19
30
|
event_bus: EventBus
|
|
20
31
|
|
|
21
32
|
def __init__(self, event_bus: EventBus, telegram_protocol: TelegramProtocol):
|
|
33
|
+
"""Initialize the HomeKit Conbus service.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
event_bus: Event bus instance.
|
|
37
|
+
telegram_protocol: Telegram protocol instance.
|
|
38
|
+
"""
|
|
22
39
|
self.logger = logging.getLogger(__name__)
|
|
23
40
|
self.event_bus = event_bus
|
|
24
41
|
self.telegram_protocol = telegram_protocol
|
|
@@ -33,7 +50,11 @@ class HomeKitConbusService:
|
|
|
33
50
|
def handle_read_datapoint_request(
|
|
34
51
|
self, event: ReadDatapointFromProtocolEvent
|
|
35
52
|
) -> None:
|
|
36
|
-
"""Handle request to read datapoint from protocol.
|
|
53
|
+
"""Handle request to read datapoint from protocol.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
event: Read datapoint event with serial number and datapoint type.
|
|
57
|
+
"""
|
|
37
58
|
self.logger.debug(f"read_datapoint_request {event}")
|
|
38
59
|
|
|
39
60
|
system_function = SystemFunction.READ_DATAPOINT.value
|
|
@@ -42,6 +63,11 @@ class HomeKitConbusService:
|
|
|
42
63
|
self.telegram_protocol.sendFrame(telegram.encode())
|
|
43
64
|
|
|
44
65
|
def handle_send_write_config_event(self, event: SendWriteConfigEvent) -> None:
|
|
66
|
+
"""Handle send write config event.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
event: Write config event with configuration data.
|
|
70
|
+
"""
|
|
45
71
|
self.logger.debug(f"send_write_config_event {event}")
|
|
46
72
|
|
|
47
73
|
# Format data as output_number:level (e.g., "02:050")
|
|
@@ -54,6 +80,11 @@ class HomeKitConbusService:
|
|
|
54
80
|
self.telegram_protocol.sendFrame(telegram.encode())
|
|
55
81
|
|
|
56
82
|
def handle_send_action_event(self, event: SendActionEvent) -> None:
|
|
83
|
+
"""Handle send action event.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
event: Send action event with action data.
|
|
87
|
+
"""
|
|
57
88
|
self.logger.debug(f"send_action_event {event}")
|
|
58
89
|
|
|
59
90
|
action_value = (
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""HomeKit Configuration Validator.
|
|
2
|
+
|
|
3
|
+
This module validates HomeKit configuration files for correctness and consistency.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
from contextlib import suppress
|
|
2
7
|
from typing import List, Set
|
|
3
8
|
|
|
@@ -9,10 +14,19 @@ class HomekitConfigValidator:
|
|
|
9
14
|
"""Validates homekit.yml configuration file for HomeKit integration."""
|
|
10
15
|
|
|
11
16
|
def __init__(self, config: HomekitConfig):
|
|
17
|
+
"""Initialize the HomeKit config validator.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
config: HomeKit configuration to validate.
|
|
21
|
+
"""
|
|
12
22
|
self.config = config
|
|
13
23
|
|
|
14
24
|
def validate_unique_accessory_names(self) -> List[str]:
|
|
15
|
-
"""Validate that all accessory names are unique.
|
|
25
|
+
"""Validate that all accessory names are unique.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of validation error messages.
|
|
29
|
+
"""
|
|
16
30
|
names: Set[str] = set()
|
|
17
31
|
errors = []
|
|
18
32
|
|
|
@@ -24,10 +38,19 @@ class HomekitConfigValidator:
|
|
|
24
38
|
return errors
|
|
25
39
|
|
|
26
40
|
def validate_service_types(self) -> List[str]:
|
|
27
|
-
"""Validate that service types are valid.
|
|
41
|
+
"""Validate that service types are valid.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
List of validation error messages.
|
|
45
|
+
"""
|
|
28
46
|
valid_services = {"lightbulb", "outlet", "dimminglight"}
|
|
29
47
|
errors = [
|
|
30
|
-
|
|
48
|
+
(
|
|
49
|
+
f"Invalid "
|
|
50
|
+
f"service type '{accessory.service}' "
|
|
51
|
+
f"for accessory '{accessory.name}'. "
|
|
52
|
+
f"Valid types: {', '.join(valid_services)}"
|
|
53
|
+
)
|
|
31
54
|
for accessory in self.config.accessories
|
|
32
55
|
if accessory.service not in valid_services
|
|
33
56
|
]
|
|
@@ -35,7 +58,11 @@ class HomekitConfigValidator:
|
|
|
35
58
|
return errors
|
|
36
59
|
|
|
37
60
|
def validate_output_numbers(self) -> List[str]:
|
|
38
|
-
"""Validate that output numbers are positive integers.
|
|
61
|
+
"""Validate that output numbers are positive integers.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
List of validation error messages.
|
|
65
|
+
"""
|
|
39
66
|
errors = [
|
|
40
67
|
f"Invalid output number {accessory.output_number} for accessory '{accessory.name}'. Must be positive."
|
|
41
68
|
for accessory in self.config.accessories
|
|
@@ -45,7 +72,11 @@ class HomekitConfigValidator:
|
|
|
45
72
|
return errors
|
|
46
73
|
|
|
47
74
|
def validate_unique_room_names(self) -> List[str]:
|
|
48
|
-
"""Validate that all room names are unique.
|
|
75
|
+
"""Validate that all room names are unique.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of validation error messages.
|
|
79
|
+
"""
|
|
49
80
|
names: Set[str] = set()
|
|
50
81
|
errors = []
|
|
51
82
|
|
|
@@ -57,7 +88,11 @@ class HomekitConfigValidator:
|
|
|
57
88
|
return errors
|
|
58
89
|
|
|
59
90
|
def validate_room_accessory_references(self) -> List[str]:
|
|
60
|
-
"""Validate that all room accessories exist in accessories section.
|
|
91
|
+
"""Validate that all room accessories exist in accessories section.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
List of validation error messages.
|
|
95
|
+
"""
|
|
61
96
|
accessory_names = {acc.name for acc in self.config.accessories}
|
|
62
97
|
errors = []
|
|
63
98
|
|
|
@@ -71,7 +106,11 @@ class HomekitConfigValidator:
|
|
|
71
106
|
return errors
|
|
72
107
|
|
|
73
108
|
def validate_no_orphaned_accessories(self) -> List[str]:
|
|
74
|
-
"""Validate that all accessories are assigned to at least one room.
|
|
109
|
+
"""Validate that all accessories are assigned to at least one room.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
List of validation error messages.
|
|
113
|
+
"""
|
|
75
114
|
assigned_accessories: Set[str] = set()
|
|
76
115
|
for room in self.config.bridge.rooms:
|
|
77
116
|
assigned_accessories.update(room.accessories)
|
|
@@ -85,7 +124,11 @@ class HomekitConfigValidator:
|
|
|
85
124
|
return errors
|
|
86
125
|
|
|
87
126
|
def validate_no_duplicate_accessory_assignments(self) -> List[str]:
|
|
88
|
-
"""Validate that accessories are not assigned to multiple rooms.
|
|
127
|
+
"""Validate that accessories are not assigned to multiple rooms.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
List of validation error messages.
|
|
131
|
+
"""
|
|
89
132
|
assigned_accessories: Set[str] = set()
|
|
90
133
|
errors = []
|
|
91
134
|
|
|
@@ -100,7 +143,11 @@ class HomekitConfigValidator:
|
|
|
100
143
|
return errors
|
|
101
144
|
|
|
102
145
|
def validate_all(self) -> List[str]:
|
|
103
|
-
"""Run all validations and return combined errors.
|
|
146
|
+
"""Run all validations and return combined errors.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of all validation error messages.
|
|
150
|
+
"""
|
|
104
151
|
all_errors = []
|
|
105
152
|
all_errors.extend(self.validate_unique_accessory_names())
|
|
106
153
|
all_errors.extend(self.validate_service_types())
|
|
@@ -120,11 +167,21 @@ class CrossReferenceValidator:
|
|
|
120
167
|
conson_validator: ConsonConfigValidator,
|
|
121
168
|
homekit_validator: HomekitConfigValidator,
|
|
122
169
|
):
|
|
170
|
+
"""Initialize the cross-reference validator.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
conson_validator: Conson configuration validator.
|
|
174
|
+
homekit_validator: HomeKit configuration validator.
|
|
175
|
+
"""
|
|
123
176
|
self.conson_validator = conson_validator
|
|
124
177
|
self.homekit_validator = homekit_validator
|
|
125
178
|
|
|
126
179
|
def validate_serial_number_references(self) -> List[str]:
|
|
127
|
-
"""Validate that all accessory serial numbers exist in conson configuration.
|
|
180
|
+
"""Validate that all accessory serial numbers exist in conson configuration.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
List of validation error messages.
|
|
184
|
+
"""
|
|
128
185
|
conson_serials = self.conson_validator.get_all_serial_numbers()
|
|
129
186
|
errors = [
|
|
130
187
|
f"Accessory '{accessory.name}' references unknown serial number {accessory.serial_number}"
|
|
@@ -135,7 +192,11 @@ class CrossReferenceValidator:
|
|
|
135
192
|
return errors
|
|
136
193
|
|
|
137
194
|
def validate_output_capabilities(self) -> List[str]:
|
|
138
|
-
"""Validate that output numbers are within module capabilities.
|
|
195
|
+
"""Validate that output numbers are within module capabilities.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
List of validation error messages.
|
|
199
|
+
"""
|
|
139
200
|
errors = []
|
|
140
201
|
|
|
141
202
|
for accessory in self.homekit_validator.config.accessories:
|
|
@@ -162,13 +223,20 @@ class CrossReferenceValidator:
|
|
|
162
223
|
|
|
163
224
|
if accessory.output_number > max_outputs:
|
|
164
225
|
errors.append(
|
|
165
|
-
f"Accessory '{accessory.name}'
|
|
226
|
+
f"Accessory '{accessory.name}' "
|
|
227
|
+
f"output {accessory.output_number} "
|
|
228
|
+
f"exceeds module '{module.name}' ({module.module_type}) "
|
|
229
|
+
f"limit of {max_outputs}"
|
|
166
230
|
)
|
|
167
231
|
|
|
168
232
|
return errors
|
|
169
233
|
|
|
170
234
|
def validate_all(self) -> List[str]:
|
|
171
|
-
"""Run all cross-reference validations and return combined errors.
|
|
235
|
+
"""Run all cross-reference validations and return combined errors.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
List of all cross-reference validation error messages.
|
|
239
|
+
"""
|
|
172
240
|
all_errors = []
|
|
173
241
|
all_errors.extend(self.validate_serial_number_references())
|
|
174
242
|
all_errors.extend(self.validate_output_capabilities())
|
|
@@ -179,6 +247,12 @@ class ConfigValidationService:
|
|
|
179
247
|
"""Main service for validating HomeKit configuration coherence."""
|
|
180
248
|
|
|
181
249
|
def __init__(self, conson_config_path: str, homekit_config_path: str):
|
|
250
|
+
"""Initialize the config validation service.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
conson_config_path: Path to conson.yml configuration file.
|
|
254
|
+
homekit_config_path: Path to homekit.yml configuration file.
|
|
255
|
+
"""
|
|
182
256
|
from xp.models.homekit.homekit_config import HomekitConfig
|
|
183
257
|
from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
|
|
184
258
|
|
|
@@ -192,7 +266,11 @@ class ConfigValidationService:
|
|
|
192
266
|
)
|
|
193
267
|
|
|
194
268
|
def validate_all(self) -> dict:
|
|
195
|
-
"""Run all validations and return organized results.
|
|
269
|
+
"""Run all validations and return organized results.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Dictionary containing validation results and error counts.
|
|
273
|
+
"""
|
|
196
274
|
conson_errors = self.conson_validator.validate_all()
|
|
197
275
|
homekit_errors = self.homekit_validator.validate_all()
|
|
198
276
|
cross_errors = self.cross_validator.validate_all()
|
|
@@ -209,7 +287,11 @@ class ConfigValidationService:
|
|
|
209
287
|
}
|
|
210
288
|
|
|
211
289
|
def print_config_summary(self) -> str:
|
|
212
|
-
"""Generate a summary of the configuration.
|
|
290
|
+
"""Generate a summary of the configuration.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
String containing configuration summary.
|
|
294
|
+
"""
|
|
213
295
|
summary = [
|
|
214
296
|
f"Conson Modules: {len(self.conson_config.root)}",
|
|
215
297
|
f"HomeKit Accessories: {len(self.homekit_config.accessories)}",
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""Conson Configuration Validator for HomeKit.
|
|
2
|
+
|
|
3
|
+
This module validates conson.yml configuration files for HomeKit integration.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
from typing import List, Set
|
|
2
7
|
|
|
3
8
|
from xp.models.homekit.homekit_conson_config import (
|
|
@@ -10,10 +15,19 @@ class ConsonConfigValidator:
|
|
|
10
15
|
"""Validates conson.yml configuration file for HomeKit integration."""
|
|
11
16
|
|
|
12
17
|
def __init__(self, config: ConsonModuleListConfig):
|
|
18
|
+
"""Initialize the Conson config validator.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
config: Conson module list configuration to validate.
|
|
22
|
+
"""
|
|
13
23
|
self.config = config
|
|
14
24
|
|
|
15
25
|
def validate_unique_names(self) -> List[str]:
|
|
16
|
-
"""Validate that all module names are unique.
|
|
26
|
+
"""Validate that all module names are unique.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of validation error messages.
|
|
30
|
+
"""
|
|
17
31
|
names: Set[str] = set()
|
|
18
32
|
errors = []
|
|
19
33
|
|
|
@@ -25,7 +39,11 @@ class ConsonConfigValidator:
|
|
|
25
39
|
return errors
|
|
26
40
|
|
|
27
41
|
def validate_unique_serial_numbers(self) -> List[str]:
|
|
28
|
-
"""Validate that all serial numbers are unique.
|
|
42
|
+
"""Validate that all serial numbers are unique.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List of validation error messages.
|
|
46
|
+
"""
|
|
29
47
|
serials: Set[str] = set()
|
|
30
48
|
errors = []
|
|
31
49
|
|
|
@@ -37,7 +55,11 @@ class ConsonConfigValidator:
|
|
|
37
55
|
return errors
|
|
38
56
|
|
|
39
57
|
def validate_module_type_codes(self) -> List[str]:
|
|
40
|
-
"""Validate module type code ranges.
|
|
58
|
+
"""Validate module type code ranges.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of validation error messages.
|
|
62
|
+
"""
|
|
41
63
|
errors = [
|
|
42
64
|
f"Invalid module_type_code {module.module_type_code} for module {module.name}. Must be between 1 and 255."
|
|
43
65
|
for module in self.config.root
|
|
@@ -47,7 +69,11 @@ class ConsonConfigValidator:
|
|
|
47
69
|
return errors
|
|
48
70
|
|
|
49
71
|
def validate_network_config(self) -> List[str]:
|
|
50
|
-
"""Validate IP/port configuration.
|
|
72
|
+
"""Validate IP/port configuration.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
List of validation error messages.
|
|
76
|
+
"""
|
|
51
77
|
errors = [
|
|
52
78
|
f"Invalid conbus_port {module.conbus_port} for module {module.name}. Must be between 1 and 65535."
|
|
53
79
|
for module in self.config.root
|
|
@@ -57,7 +83,11 @@ class ConsonConfigValidator:
|
|
|
57
83
|
return errors
|
|
58
84
|
|
|
59
85
|
def validate_all(self) -> List[str]:
|
|
60
|
-
"""Run all validations and return combined errors.
|
|
86
|
+
"""Run all validations and return combined errors.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
List of all validation error messages.
|
|
90
|
+
"""
|
|
61
91
|
all_errors = []
|
|
62
92
|
all_errors.extend(self.validate_unique_names())
|
|
63
93
|
all_errors.extend(self.validate_unique_serial_numbers())
|
|
@@ -66,12 +96,26 @@ class ConsonConfigValidator:
|
|
|
66
96
|
return all_errors
|
|
67
97
|
|
|
68
98
|
def get_module_by_serial(self, serial_number: str) -> ConsonModuleConfig:
|
|
69
|
-
"""Get module configuration by serial number.
|
|
99
|
+
"""Get module configuration by serial number.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
serial_number: Serial number of the module to find.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Module configuration for the specified serial number.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
ValueError: If module with serial number is not found.
|
|
109
|
+
"""
|
|
70
110
|
for module in self.config.root:
|
|
71
111
|
if module.serial_number == serial_number:
|
|
72
112
|
return module
|
|
73
113
|
raise ValueError(f"Module with serial number {serial_number} not found")
|
|
74
114
|
|
|
75
115
|
def get_all_serial_numbers(self) -> Set[str]:
|
|
76
|
-
"""Get all serial numbers from the configuration.
|
|
116
|
+
"""Get all serial numbers from the configuration.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Set of all serial numbers in the configuration.
|
|
120
|
+
"""
|
|
77
121
|
return {module.serial_number for module in self.config.root}
|